View Javadoc

1   /*
2    * Copyright 2005-2007 The Kuali Foundation
3    *
4    *
5    * Licensed under the Educational Community License, Version 2.0 (the "License");
6    * you may not use this file except in compliance with the License.
7    * You may obtain a copy of the License at
8    *
9    * http://www.opensource.org/licenses/ecl2.php
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.kuali.rice.kew.xml;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.sql.Timestamp;
22  import java.text.ParseException;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  
27  import javax.xml.parsers.ParserConfigurationException;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.jdom.Document;
31  import org.jdom.Element;
32  import org.jdom.JDOMException;
33  import org.kuali.rice.core.util.RiceConstants;
34  import org.kuali.rice.kew.doctype.bo.DocumentType;
35  import org.kuali.rice.kew.exception.InvalidXmlException;
36  import org.kuali.rice.kew.rule.Role;
37  import org.kuali.rice.kew.rule.RuleBaseValues;
38  import org.kuali.rice.kew.rule.RuleDelegation;
39  import org.kuali.rice.kew.rule.RuleExpressionDef;
40  import org.kuali.rice.kew.rule.RuleResponsibility;
41  import org.kuali.rice.kew.rule.bo.RuleTemplate;
42  import org.kuali.rice.kew.service.KEWServiceLocator;
43  import org.kuali.rice.kew.util.KEWConstants;
44  import org.kuali.rice.kew.util.Utilities;
45  import org.kuali.rice.kew.util.XmlHelper;
46  import org.kuali.rice.kim.bo.Group;
47  import org.kuali.rice.kim.bo.entity.KimPrincipal;
48  import org.kuali.rice.kim.service.KIMServiceLocator;
49  import org.xml.sax.SAXException;
50  
51  
52  /**
53   * Parses rules from XML.
54   *
55   * @see RuleBaseValues
56   *
57   * @author Kuali Rice Team (rice.collab@kuali.org)
58   */
59  public class RuleXmlParser implements XmlConstants {
60  
61      private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RuleXmlParser.class);
62  
63      /**
64       * Priority to use if rule responsibility omits priority
65       */
66      private static final int DEFAULT_RULE_PRIORITY = 1;
67      /**
68       * Value of Force Action flag if omitted; default to false, we will NOT force action for approvals
69       */
70      private static final boolean DEFAULT_FORCE_ACTION = false;
71      /**
72       * Default approve policy, if omitted; defaults to FIRST_APPROVE, the request will be satisfied by the first approval
73       */
74      private static final String DEFAULT_APPROVE_POLICY = KEWConstants.APPROVE_POLICY_FIRST_APPROVE;
75      /**
76       * Default action requested, if omitted; defaults to "A"pprove
77       */
78      private static final String DEFAULT_ACTION_REQUESTED = KEWConstants.ACTION_REQUEST_APPROVE_REQ;
79  
80      public List<RuleDelegation> parseRuleDelegations(InputStream input) throws IOException, InvalidXmlException {
81      	try {
82              Document doc = XmlHelper.trimSAXXml(input);
83              Element root = doc.getRootElement();
84              return parseRuleDelegations(root);
85          } catch (JDOMException e) {
86              throw new InvalidXmlException("Parse error.", e);
87          } catch (SAXException e){
88              throw new InvalidXmlException("Parse error.",e);
89          } catch(ParserConfigurationException e){
90              throw new InvalidXmlException("Parse error.",e);
91          }
92      }
93      
94      public List<RuleBaseValues> parseRules(InputStream input) throws IOException, InvalidXmlException {
95          try {
96              Document doc = XmlHelper.trimSAXXml(input);
97              Element root = doc.getRootElement();
98              return parseRules(root);
99          } catch (JDOMException e) {
100             throw new InvalidXmlException("Parse error.", e);
101         } catch (SAXException e){
102             throw new InvalidXmlException("Parse error.",e);
103         } catch(ParserConfigurationException e){
104             throw new InvalidXmlException("Parse error.",e);
105         }
106     }
107 
108     /**
109      * Parses and saves rules
110      * @param element top-level 'data' element which should contain a <rules> child element
111      * @throws InvalidXmlException
112      */
113     public List<RuleBaseValues> parseRules(Element element) throws InvalidXmlException {
114     	List<RuleBaseValues> rulesToSave = new ArrayList<RuleBaseValues>();
115         for (Element rulesElement: (List<Element>) element.getChildren(RULES, RULE_NAMESPACE)) {
116             for (Element ruleElement: (List<Element>) rulesElement.getChildren(RULE, RULE_NAMESPACE)) {
117                 RuleBaseValues rule = parseRule(ruleElement);
118                 rulesToSave.add(rule);
119             }
120         }
121         checkForDuplicateRules(rulesToSave);
122         return KEWServiceLocator.getRuleService().saveRules(rulesToSave, false);
123     }
124     
125     /**
126      * Parses and saves rule delegations
127      * @param element top-level 'data' element which should contain a <rules> child element
128      * @throws InvalidXmlException
129      */
130     public List<RuleDelegation> parseRuleDelegations(Element element) throws InvalidXmlException {
131     	List<RuleDelegation> ruleDelegationsToSave = new ArrayList<RuleDelegation>();
132         for (Element ruleDelegationsElement: (List<Element>) element.getChildren(RULE_DELEGATIONS, RULE_NAMESPACE)) {
133             for (Element ruleDelegationElement: (List<Element>) ruleDelegationsElement.getChildren(RULE_DELEGATION, RULE_NAMESPACE)) {
134                 RuleDelegation ruleDelegation = parseRuleDelegation(ruleDelegationElement);
135                 ruleDelegationsToSave.add(ruleDelegation);
136             }
137         }
138         //checkForDuplicateRuleDelegations(ruleDelegationsToSave);
139         return KEWServiceLocator.getRuleService().saveRuleDelegations(ruleDelegationsToSave, false);
140     }
141     
142     /**
143      * Checks for rules in the List that duplicate other Rules already in the system 
144      */
145     private void checkForDuplicateRules(List<RuleBaseValues> rules) throws InvalidXmlException {
146     	for (RuleBaseValues rule : rules) {
147     		if (StringUtils.isBlank(rule.getName())) {
148     			LOG.debug("Checking for rule duplication on an anonymous rule.");
149     			checkRuleForDuplicate(rule);
150     		}
151     	}
152     }
153     
154     /**
155      * Checks for rule delegations in the List that duplicate other Rules already in the system 
156      */
157     private void checkForDuplicateRuleDelegations(List<RuleDelegation> ruleDelegations) throws InvalidXmlException {
158     	for (RuleDelegation ruleDelegation : ruleDelegations) {
159     		if (StringUtils.isBlank(ruleDelegation.getDelegationRuleBaseValues().getName())) {
160     			LOG.debug("Checking for rule duplication on an anonymous rule delegation.");
161     			checkRuleDelegationForDuplicate(ruleDelegation);
162     		}
163     	}
164     }
165 
166     private RuleDelegation parseRuleDelegation(Element element) throws InvalidXmlException {
167     	RuleDelegation ruleDelegation = new RuleDelegation();
168     	Element parentResponsibilityElement = element.getChild(PARENT_RESPONSIBILITY, element.getNamespace());
169     	if (parentResponsibilityElement == null) {
170     		throw new InvalidXmlException("parent responsibility was not defined");
171     	}
172     	Long parentResponsibilityId = parseParentResponsibilityId(parentResponsibilityElement);
173     	String delegationType = element.getChildText(DELEGATION_TYPE, element.getNamespace());
174         if (delegationType == null || !(delegationType.equals(KEWConstants.DELEGATION_PRIMARY) || delegationType.equals(KEWConstants.DELEGATION_SECONDARY))) {
175             throw new InvalidXmlException("Invalid delegation type specified for delegate rule '" + delegationType + "'");
176         }
177         
178         ruleDelegation.setResponsibilityId(parentResponsibilityId);
179         ruleDelegation.setDelegationType(delegationType);
180         
181         Element ruleElement = element.getChild(RULE, element.getNamespace());
182         RuleBaseValues rule = parseRule(ruleElement);
183         rule.setDelegateRule(true);
184         ruleDelegation.setDelegationRuleBaseValues(rule);
185     	
186     	return ruleDelegation;
187     }
188     
189     private Long parseParentResponsibilityId(Element element) throws InvalidXmlException {
190     	String responsibilityId = element.getChildText(RESPONSIBILITY_ID, element.getNamespace());
191     	if (!StringUtils.isBlank(responsibilityId)) {
192     		return Long.valueOf(responsibilityId);
193     	}
194     	String parentRuleName = element.getChildText(PARENT_RULE_NAME, element.getNamespace());
195     	if (StringUtils.isBlank(parentRuleName)) {
196     		throw new InvalidXmlException("One of responsibilityId or parentRuleName needs to be defined");
197     	}
198     	RuleBaseValues parentRule = KEWServiceLocator.getRuleService().getRuleByName(parentRuleName);
199     	if (parentRule == null) {
200     		throw new InvalidXmlException("Could find the parent rule with name '" + parentRuleName + "'");
201     	}
202     	RuleResponsibility ruleResponsibilityNameAndType = parseResponsibilityNameAndType(element);
203     	if (ruleResponsibilityNameAndType == null) {
204     		throw new InvalidXmlException("Could not locate a valid responsibility declaration for the parent responsibility.");
205     	}
206     	Long parentResponsibilityId = KEWServiceLocator.getRuleService().findResponsibilityIdForRule(parentRuleName, 
207     			ruleResponsibilityNameAndType.getRuleResponsibilityName(),
208     			ruleResponsibilityNameAndType.getRuleResponsibilityType());
209     	if (parentResponsibilityId == null) {
210     		throw new InvalidXmlException("Failed to locate parent responsibility for rule with name '" + parentRuleName + "' and responsibility " + ruleResponsibilityNameAndType);
211     	}
212     	return parentResponsibilityId;
213     }
214     
215     /**
216      * Parses, and only parses, a rule definition (be it a top-level rule, or a rule delegation).  This method will
217      * NOT dirty or save any existing data, it is side-effect-free.
218      * @param element the rule element
219      * @param ruleDelegation the ruleDelegation object if this rule is being parsed as a delegation
220      * @return a new RuleBaseValues object which is not yet saved
221      * @throws InvalidXmlException
222      */
223     private RuleBaseValues parseRule(Element element) throws InvalidXmlException {
224         String name = element.getChildText(NAME, element.getNamespace());
225         RuleBaseValues rule = createRule(name);
226         
227         setDefaultRuleValues(rule);
228         rule.setName(name);
229         
230         String toDatestr = element.getChildText( TO_DATE, element.getNamespace());
231         String fromDatestr = element.getChildText( FROM_DATE, element.getNamespace());
232         rule.setToDate(formatDate("toDate", toDatestr));
233         rule.setFromDate(formatDate("fromDate", fromDatestr));
234 
235         String description = element.getChildText(DESCRIPTION, element.getNamespace());
236         if (StringUtils.isBlank(description)) {
237             throw new InvalidXmlException("Rule must have a description.");
238         }
239                 
240         String documentTypeName = element.getChildText(DOCUMENT_TYPE, element.getNamespace());
241         if (StringUtils.isBlank(documentTypeName)) {
242         	throw new InvalidXmlException("Rule must have a document type.");
243         }
244         DocumentType documentType = KEWServiceLocator.getDocumentTypeService().findByName(documentTypeName);
245         if (documentType == null) {
246         	throw new InvalidXmlException("Could not locate document type '" + documentTypeName + "'");
247         }
248 
249         RuleTemplate ruleTemplate = null;
250         String ruleTemplateName = element.getChildText(RULE_TEMPLATE, element.getNamespace());        
251         Element ruleExtensionsElement = element.getChild(RULE_EXTENSIONS, element.getNamespace());
252         if (!StringUtils.isBlank(ruleTemplateName)) {
253         	ruleTemplate = KEWServiceLocator.getRuleTemplateService().findByRuleTemplateName(ruleTemplateName);
254         	if (ruleTemplate == null) {
255         		throw new InvalidXmlException("Could not locate rule template '" + ruleTemplateName + "'");
256         	}
257         } else {
258         	if (ruleExtensionsElement != null) {
259         		throw new InvalidXmlException("Templateless rules may not have rule extensions");
260         	}
261         }
262 
263         RuleExpressionDef ruleExpressionDef = null;
264         Element exprElement = element.getChild(RULE_EXPRESSION, element.getNamespace());
265         if (exprElement != null) {
266         	String exprType = exprElement.getAttributeValue("type");
267         	if (StringUtils.isEmpty(exprType)) {
268         		throw new InvalidXmlException("Expression type must be specified");
269         	}
270         	String expression = exprElement.getTextTrim();
271         	ruleExpressionDef = new RuleExpressionDef();
272         	ruleExpressionDef.setType(exprType);
273         	ruleExpressionDef.setExpression(expression);
274         }
275         
276         String forceActionValue = element.getChildText(FORCE_ACTION, element.getNamespace());
277         Boolean forceAction = Boolean.valueOf(DEFAULT_FORCE_ACTION);
278         if (!StringUtils.isBlank(forceActionValue)) {
279             forceAction = Boolean.valueOf(forceActionValue);
280         }
281 
282         rule.setDocTypeName(documentType.getName());
283         if (ruleTemplate != null) {
284             rule.setRuleTemplateId(ruleTemplate.getRuleTemplateId());
285             rule.setRuleTemplate(ruleTemplate);
286         }
287         if (ruleExpressionDef != null) {
288             rule.setRuleExpressionDef(ruleExpressionDef);
289         }
290         rule.setDescription(description);
291         rule.setForceAction(forceAction);
292 
293         Element responsibilitiesElement = element.getChild(RESPONSIBILITIES, element.getNamespace());
294         rule.setResponsibilities(parseResponsibilities(responsibilitiesElement, rule));
295         rule.setRuleExtensions(parseRuleExtensions(ruleExtensionsElement, rule));
296 
297         return rule;
298     }
299     
300     /**
301      * Creates the rule that the parser will populate.  If a rule with the given name
302      * already exists, it's keys and responsibilities will be copied over to the
303      * new rule.  The calling code will then sort through the original responsibilities
304      * and compare them with those being defined on the XML being parsed.
305      */
306     private RuleBaseValues createRule(String ruleName) {
307     	RuleBaseValues rule = new RuleBaseValues();
308     	RuleBaseValues existingRule = KEWServiceLocator.getRuleService().getRuleByName(ruleName);
309     	if (existingRule != null) {
310     		// copy keys and responsibiliities from the existing rule
311     		rule.setRuleBaseValuesId(existingRule.getRuleBaseValuesId());
312     		rule.setPreviousVersionId(existingRule.getPreviousVersionId());
313     		rule.setPreviousVersion(existingRule.getPreviousVersion());
314     		rule.setResponsibilities(existingRule.getResponsibilities());
315     	}
316     	return rule;
317     }
318 
319     /**
320      * Checks to see whether this anonymous rule duplicates an existing rule.
321      * Currently the uniqueness is on ruleResponsibilityName, and extension key/values.
322      * @param rule the rule to check
323      * @throws InvalidXmlException if this incoming rule duplicates an existing rule
324      */
325     private void checkRuleForDuplicate(RuleBaseValues rule) throws InvalidXmlException {
326         Long ruleId = KEWServiceLocator.getRuleService().getDuplicateRuleId(rule);
327         if (ruleId != null) {
328         	throw new InvalidXmlException("Rule '" + rule.getDescription() + "' on doc '" + rule.getDocTypeName() + "' is a duplicate of rule with rule Id " + ruleId);
329         }
330     }
331     
332     private void checkRuleDelegationForDuplicate(RuleDelegation ruleDelegation) throws InvalidXmlException {
333     	checkRuleForDuplicate(ruleDelegation.getDelegationRuleBaseValues());
334     }
335 
336     private void setDefaultRuleValues(RuleBaseValues rule) throws InvalidXmlException {
337         rule.setForceAction(Boolean.FALSE);
338         rule.setActivationDate(new Timestamp(System.currentTimeMillis()));
339         rule.setActiveInd(Boolean.TRUE);
340         rule.setCurrentInd(Boolean.TRUE);
341         rule.setTemplateRuleInd(Boolean.FALSE);
342         rule.setVersionNbr(new Integer(0));
343         rule.setDelegateRule(false);
344     }
345 
346     private List<RuleResponsibility> parseResponsibilities(Element element, RuleBaseValues rule) throws InvalidXmlException {
347         if (element == null) {
348             return new ArrayList<RuleResponsibility>(0);
349         }
350         List<RuleResponsibility> existingResponsibilities = rule.getResponsibilities();
351         List<RuleResponsibility> responsibilities = new ArrayList<RuleResponsibility>();
352         List responsibilityElements = element.getChildren(RESPONSIBILITY, element.getNamespace());
353         for (Iterator iterator = responsibilityElements.iterator(); iterator.hasNext();) {
354             Element responsibilityElement = (Element) iterator.next();
355             RuleResponsibility responsibility = parseResponsibility(responsibilityElement, rule);
356             reconcileWithExistingResponsibility(responsibility, existingResponsibilities);
357             responsibilities.add(responsibility);
358         }
359         if (responsibilities.size() == 0) {
360             throw new InvalidXmlException("Rule responsibility list must have at least one responsibility.");
361         }
362         return responsibilities;
363     }
364 
365     public RuleResponsibility parseResponsibility(Element element, RuleBaseValues rule) throws InvalidXmlException {
366         RuleResponsibility responsibility = new RuleResponsibility();
367         responsibility.setRuleBaseValues(rule);
368         String actionRequested = null;
369         String priority = null;
370         actionRequested = element.getChildText(ACTION_REQUESTED, element.getNamespace());
371         if (StringUtils.isBlank(actionRequested)) {
372         	actionRequested = DEFAULT_ACTION_REQUESTED;
373         }
374         priority = element.getChildText(PRIORITY, element.getNamespace());
375         if (StringUtils.isBlank(priority)) {
376         	priority = String.valueOf(DEFAULT_RULE_PRIORITY);
377         }
378         String approvePolicy = element.getChildText(APPROVE_POLICY, element.getNamespace());
379         Element delegations = element.getChild(DELEGATIONS, element.getNamespace());
380         if (actionRequested == null) {
381             throw new InvalidXmlException("actionRequested is required on responsibility");
382         }
383         if (!actionRequested.equals(KEWConstants.ACTION_REQUEST_COMPLETE_REQ) && !actionRequested.equals(KEWConstants.ACTION_REQUEST_APPROVE_REQ) && !actionRequested.equals(KEWConstants.ACTION_REQUEST_ACKNOWLEDGE_REQ) && !actionRequested.equals(KEWConstants.ACTION_REQUEST_FYI_REQ)) {
384             throw new InvalidXmlException("Invalid action requested code '" + actionRequested + "'");
385         }
386         if (StringUtils.isBlank(approvePolicy)) {
387             approvePolicy = DEFAULT_APPROVE_POLICY;
388         }
389         if (!approvePolicy.equals(KEWConstants.APPROVE_POLICY_ALL_APPROVE) && !approvePolicy.equals(KEWConstants.APPROVE_POLICY_FIRST_APPROVE)) {
390             throw new InvalidXmlException("Invalid approve policy '" + approvePolicy + "'");
391         }
392         Integer priorityNumber = Integer.valueOf(priority);
393         responsibility.setActionRequestedCd(actionRequested);
394         responsibility.setPriority(priorityNumber);
395         responsibility.setApprovePolicy(approvePolicy);
396         
397         RuleResponsibility responsibilityNameAndType = parseResponsibilityNameAndType(element);
398         if (responsibilityNameAndType == null) {
399         	throw new InvalidXmlException("Could not locate a valid responsibility declaration on a responsibility on rule with description '" + rule.getDescription() + "'");
400         }
401         if (responsibilityNameAndType.getRuleResponsibilityType().equals(KEWConstants.RULE_RESPONSIBILITY_GROUP_ID)
402         		&& responsibility.getApprovePolicy().equals(KEWConstants.APPROVE_POLICY_ALL_APPROVE)) {
403         	throw new InvalidXmlException("Invalid approve policy '" + approvePolicy + "'.  This policy is not supported with Groups.");
404         }
405         responsibility.setRuleResponsibilityName(responsibilityNameAndType.getRuleResponsibilityName());
406         responsibility.setRuleResponsibilityType(responsibilityNameAndType.getRuleResponsibilityType());
407         
408         return responsibility;
409     }
410 
411     public RuleResponsibility parseResponsibilityNameAndType(Element element) throws InvalidXmlException {
412     	RuleResponsibility responsibility = new RuleResponsibility();
413     	
414     	String principalId = element.getChildText(PRINCIPAL_ID, element.getNamespace());
415         String principalName = element.getChildText(PRINCIPAL_NAME, element.getNamespace());
416         String groupId = element.getChildText(GROUP_ID, element.getNamespace());
417         Element groupNameElement = element.getChild(GROUP_NAME, element.getNamespace());
418         String role = element.getChildText(ROLE, element.getNamespace());
419         Element roleNameElement = element.getChild(ROLE_NAME, element.getNamespace());
420         
421         String user = element.getChildText(USER, element.getNamespace());
422         String workgroup = element.getChildText(WORKGROUP, element.getNamespace());
423         
424         if (!StringUtils.isEmpty(user)) {
425         	principalName = user;
426         	LOG.warn("Rule XML is using deprecated element 'user', please use 'principalName' instead.");
427         }
428         
429         // in code below, we allow core config parameter replacement in responsibilities
430         if (!StringUtils.isBlank(principalId)) {
431         	principalId = Utilities.substituteConfigParameters(principalId);
432         	KimPrincipal principal = KIMServiceLocator.getIdentityManagementService().getPrincipal(principalId);
433             if (principal == null) {
434             	throw new InvalidXmlException("Could not locate principal with the given id: " + principalId);
435             }
436             responsibility.setRuleResponsibilityName(principalId);
437             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_WORKFLOW_ID);
438         } else if (!StringUtils.isBlank(principalName)) {
439         	principalName = Utilities.substituteConfigParameters(principalName);
440         	KimPrincipal principal = KIMServiceLocator.getIdentityManagementService().getPrincipalByPrincipalName(principalName);
441             if (principal == null) {
442             	throw new InvalidXmlException("Could not locate principal with the given name: " + principalName);
443             }
444             responsibility.setRuleResponsibilityName(principal.getPrincipalId());
445             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_WORKFLOW_ID);
446         } else if (!StringUtils.isBlank(groupId)) {
447             groupId = Utilities.substituteConfigParameters(groupId);
448             Group group = KIMServiceLocator.getIdentityManagementService().getGroup(groupId);
449             if (group == null) {
450                 throw new InvalidXmlException("Could not locate group with the given id: " + groupId);
451             }
452             responsibility.setRuleResponsibilityName(groupId);
453             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_GROUP_ID);
454         } else if (groupNameElement != null) {
455         	String groupName = groupNameElement.getText();
456         	String groupNamespace = groupNameElement.getAttributeValue(NAMESPACE);
457         	if (StringUtils.isBlank(groupName)) {
458         		throw new InvalidXmlException("Group name element has no value");
459         	}
460         	if (StringUtils.isBlank(groupNamespace)) {
461         		throw new InvalidXmlException("namespace attribute must be specified");
462         	}
463             groupName = Utilities.substituteConfigParameters(groupName);
464             groupNamespace = Utilities.substituteConfigParameters(groupNamespace);
465             Group group = KIMServiceLocator.getIdentityManagementService().getGroupByName(groupNamespace, groupName);
466             if (group == null) {
467                 throw new InvalidXmlException("Could not locate group with the given namespace: " + groupNamespace + " and name: " + groupName);
468             }
469             responsibility.setRuleResponsibilityName(group.getGroupId());
470             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_GROUP_ID);
471         } else if (!StringUtils.isBlank(role)) {
472         	role = Utilities.substituteConfigParameters(role);
473         	responsibility.setRuleResponsibilityName(role);
474             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_ROLE_ID);
475         } else if (roleNameElement != null) {
476         	String roleName = roleNameElement.getText();
477         	String attributeClassName = roleNameElement.getAttributeValue(ATTRIBUTE_CLASS_NAME);
478         	if (StringUtils.isBlank(roleName)) {
479         		throw new InvalidXmlException("Role name element has no value");
480         	}
481         	if (StringUtils.isBlank(attributeClassName)) {
482         		throw new InvalidXmlException("attributeClassName attribute must be specified");
483         	}
484         	roleName = Utilities.substituteConfigParameters(roleName);
485         	attributeClassName = Utilities.substituteConfigParameters(attributeClassName);
486         	responsibility.setRuleResponsibilityName(Role.constructRoleValue(attributeClassName, roleName));
487             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_ROLE_ID);
488         } else if (!StringUtils.isBlank(workgroup)) {
489         	LOG.warn("Rule XML is using deprecated element 'workgroup', please use 'groupName' instead.");
490             workgroup = Utilities.substituteConfigParameters(workgroup);
491             String workgroupNamespace = Utilities.parseGroupNamespaceCode(workgroup);
492             String workgroupName = Utilities.parseGroupName(workgroup);
493 
494             Group workgroupObject = KIMServiceLocator.getIdentityManagementService().getGroupByName(workgroupNamespace, workgroupName);
495             if (workgroupObject == null) {
496                 throw new InvalidXmlException("Could not locate workgroup: " + workgroup);
497             }
498             responsibility.setRuleResponsibilityName(workgroupObject.getGroupId());
499             responsibility.setRuleResponsibilityType(KEWConstants.RULE_RESPONSIBILITY_GROUP_ID);
500         } else {
501         	return null;
502         }
503         
504         return responsibility;
505     }
506     
507     /**
508      * Attempts to reconcile the given RuleResponsibility with the list of existing responsibilities (in the case of a
509      * rule being updated via the XML).  This goal of this code is to copy responsibility ids from existing responsibilities
510      * to the new responsibility where appropriate.  The code will attempt to find exact matches based on the values found
511      * on the responsibilities.
512      */
513     private void reconcileWithExistingResponsibility(RuleResponsibility responsibility, List<RuleResponsibility> existingResponsibilities) {
514     	if (existingResponsibilities == null || existingResponsibilities.isEmpty()) {
515     		return;
516     	}
517     	RuleResponsibility exactMatch = null;
518     	for (RuleResponsibility existingResponsibility : existingResponsibilities) {
519     		if (isExactResponsibilityMatch(responsibility, existingResponsibility)) {
520     			exactMatch = existingResponsibility;
521     			break;
522     		}
523     	}
524     	if (exactMatch != null) {
525     		responsibility.setResponsibilityId(exactMatch.getResponsibilityId());
526     	}
527     }
528     
529     /**
530      * Checks if the given responsibilities are exact matches of one another.
531      */
532     private boolean isExactResponsibilityMatch(RuleResponsibility newResponsibility, RuleResponsibility existingResponsibility) {
533     	if (existingResponsibility.getResponsibilityId().equals(newResponsibility.getResponsibilityId())) {
534     		return true;
535     	}
536     	if (existingResponsibility.getRuleResponsibilityName().equals(newResponsibility.getRuleResponsibilityName()) &&
537     			existingResponsibility.getRuleResponsibilityType().equals(newResponsibility.getRuleResponsibilityType()) &&
538     			existingResponsibility.getApprovePolicy().equals(newResponsibility.getApprovePolicy()) &&
539     			existingResponsibility.getActionRequestedCd().equals(newResponsibility.getActionRequestedCd()) &&
540     			existingResponsibility.getPriority().equals(newResponsibility.getPriority())) {
541     		return true;
542     	}
543     	return false;
544     }
545 
546     private List parseRuleExtensions(Element element, RuleBaseValues rule) throws InvalidXmlException {
547         if (element == null) {
548             return new ArrayList();
549         }
550         RuleExtensionXmlParser parser = new RuleExtensionXmlParser();
551         return parser.parseRuleExtensions(element, rule);
552     }
553     
554     public Timestamp formatDate(String dateLabel, String dateString) throws InvalidXmlException {
555     	if (StringUtils.isBlank(dateString)) {
556     		return null;
557     	}
558     	try {
559     		return new Timestamp(RiceConstants.getDefaultDateFormat().parse(dateString).getTime());
560     	} catch (ParseException e) {
561     		throw new InvalidXmlException(dateLabel + " is not in the proper format.  Should have been: " + RiceConstants.DEFAULT_DATE_FORMAT_PATTERN);
562     	}
563     }
564     
565 }