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