View Javadoc

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