001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kew.rule.service.impl;
017
018import org.apache.commons.beanutils.PropertyUtils;
019import org.apache.commons.lang.ObjectUtils;
020import org.apache.commons.lang.StringUtils;
021import org.jdom.Element;
022import org.kuali.rice.core.api.impex.ExportDataSet;
023import org.kuali.rice.core.api.impex.xml.XmlConstants;
024import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
025import org.kuali.rice.core.api.util.RiceConstants;
026import org.kuali.rice.core.api.util.collect.CollectionUtils;
027import org.kuali.rice.core.impl.cache.DistributedCacheManagerDecorator;
028import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
029import org.kuali.rice.kew.actionrequest.service.ActionRequestService;
030import org.kuali.rice.kew.api.KewApiServiceLocator;
031import org.kuali.rice.kew.api.WorkflowDocument;
032import org.kuali.rice.kew.api.WorkflowDocumentFactory;
033import org.kuali.rice.kew.api.WorkflowRuntimeException;
034import org.kuali.rice.kew.api.action.ActionRequestPolicy;
035import org.kuali.rice.kew.api.rule.Rule;
036import org.kuali.rice.kew.api.rule.RuleDelegation;
037import org.kuali.rice.kew.api.rule.RuleExtension;
038import org.kuali.rice.kew.api.rule.RuleResponsibility;
039import org.kuali.rice.kew.api.validation.RuleValidationContext;
040import org.kuali.rice.kew.api.validation.ValidationResults;
041import org.kuali.rice.kew.doctype.bo.DocumentType;
042import org.kuali.rice.kew.doctype.service.DocumentTypeService;
043import org.kuali.rice.kew.exception.WorkflowServiceErrorException;
044import org.kuali.rice.kew.exception.WorkflowServiceErrorImpl;
045import org.kuali.rice.kew.impl.KewImplConstants;
046import org.kuali.rice.kew.responsibility.service.ResponsibilityIdService;
047import org.kuali.rice.kew.routeheader.DocumentRouteHeaderValue;
048import org.kuali.rice.kew.routeheader.service.RouteHeaderService;
049import org.kuali.rice.kew.rule.RuleBaseValues;
050import org.kuali.rice.kew.rule.RuleDelegationBo;
051import org.kuali.rice.kew.rule.RuleExtensionBo;
052import org.kuali.rice.kew.rule.RuleExtensionValue;
053import org.kuali.rice.kew.rule.RuleResponsibilityBo;
054import org.kuali.rice.kew.rule.RuleRoutingDefinition;
055import org.kuali.rice.kew.rule.RuleValidationAttribute;
056import org.kuali.rice.kew.rule.bo.RuleTemplateAttributeBo;
057import org.kuali.rice.kew.rule.bo.RuleTemplateBo;
058import org.kuali.rice.kew.rule.dao.RuleDAO;
059import org.kuali.rice.kew.rule.dao.RuleResponsibilityDAO;
060import org.kuali.rice.kew.rule.service.RuleDelegationService;
061import org.kuali.rice.kew.rule.service.RuleServiceInternal;
062import org.kuali.rice.kew.rule.service.RuleTemplateService;
063import org.kuali.rice.kew.service.KEWServiceLocator;
064import org.kuali.rice.kew.api.KewApiConstants;
065import org.kuali.rice.kew.util.PerformanceLogger;
066import org.kuali.rice.kew.xml.RuleXmlParser;
067import org.kuali.rice.kew.xml.export.RuleXmlExporter;
068import org.kuali.rice.kim.api.group.Group;
069import org.kuali.rice.kim.api.group.GroupService;
070import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
071import org.kuali.rice.kim.api.services.KimApiServiceLocator;
072import org.kuali.rice.krad.UserSession;
073import org.kuali.rice.krad.util.GlobalVariables;
074import org.kuali.rice.krad.util.KRADConstants;
075import org.springframework.cache.Cache;
076
077import java.io.InputStream;
078import java.sql.Timestamp;
079import java.text.ParseException;
080import java.util.ArrayList;
081import java.util.Collection;
082import java.util.Collections;
083import java.util.Comparator;
084import java.util.HashMap;
085import java.util.HashSet;
086import java.util.Iterator;
087import java.util.List;
088import java.util.Map;
089import java.util.Set;
090import java.util.UUID;
091
092
093public class RuleServiceInternalImpl implements RuleServiceInternal {
094
095    private static final String XML_PARSE_ERROR = "general.error.parsexml";
096
097    private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(RuleServiceInternalImpl.class);
098
099    private RuleDAO ruleDAO;
100    private RuleResponsibilityDAO ruleResponsibilityDAO;
101
102    public RuleResponsibilityDAO getRuleResponsibilityDAO() {
103        return ruleResponsibilityDAO;
104    }
105
106    public RuleBaseValues getRuleByName(String name) {
107        return ruleDAO.findRuleBaseValuesByName(name);
108    }
109
110    public RuleBaseValues findDefaultRuleByRuleTemplateId(String ruleTemplateId){
111        return this.ruleDAO.findDefaultRuleByRuleTemplateId(ruleTemplateId);
112    }
113    public void setRuleResponsibilityDAO(RuleResponsibilityDAO ruleResponsibilityDAO) {
114        this.ruleResponsibilityDAO = ruleResponsibilityDAO;
115    }
116
117    public void save2(RuleBaseValues ruleBaseValues) throws Exception {
118        save2(ruleBaseValues, null, true);
119    }
120
121    public void save2(RuleBaseValues ruleBaseValues, RuleDelegationBo ruleDelegation, boolean saveDelegations) throws Exception {
122        if (ruleBaseValues.getPreviousRuleId() != null) {
123            RuleBaseValues oldRule = findRuleBaseValuesById(ruleBaseValues.getPreviousRuleId());
124            ruleBaseValues.setPreviousVersion(oldRule);
125            ruleBaseValues.setCurrentInd(Boolean.FALSE);
126            ruleBaseValues.setVersionNbr(getNextVersionNumber(oldRule));
127        }
128        if (ruleBaseValues.getVersionNbr() == null) {
129            ruleBaseValues.setVersionNbr(Integer.valueOf(0));
130        }
131        if (ruleBaseValues.getCurrentInd() == null) {
132            ruleBaseValues.setCurrentInd(Boolean.FALSE);
133        }
134        // iterate through all associated responsibilities, and if they are unsaved (responsibilityId is null)
135        // set a new id on them, and recursively save any associated delegation rules
136        for (Object element : ruleBaseValues.getRuleResponsibilities()) {
137            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
138            if (responsibility.getResponsibilityId() == null) {
139                responsibility.setResponsibilityId(getResponsibilityIdService().getNewResponsibilityId());
140            }
141            if (saveDelegations) {
142                for (Object element2 : responsibility.getDelegationRules()) {
143                    RuleDelegationBo localRuleDelegation = (RuleDelegationBo) element2;
144                    save2(localRuleDelegation.getDelegationRule(), localRuleDelegation, true);
145                }
146            }
147        }
148        validate2(ruleBaseValues, ruleDelegation, null);
149        getRuleDAO().save(ruleBaseValues);
150    }
151
152    public void makeCurrent(String documentId) {
153        makeCurrent(findByDocumentId(documentId));
154    }
155
156    public void makeCurrent(List<RuleBaseValues> rules) {
157        PerformanceLogger performanceLogger = new PerformanceLogger();
158
159        boolean isGenerateRuleArs = true;
160        String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
161        if (!StringUtils.isBlank(generateRuleArs)) {
162            isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
163        }
164        Set<String> responsibilityIds = new HashSet<String>();
165        Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
166
167        Collections.sort(rules, new RuleDelegationSorter());
168        boolean delegateFirst = false;
169        for (RuleBaseValues rule : rules) {
170            performanceLogger.log("Preparing rule: " + rule.getDescription());
171
172            rule.setCurrentInd(Boolean.TRUE);
173            Timestamp date = new Timestamp(System.currentTimeMillis());
174            rule.setActivationDate(date);
175            try {
176                rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
177            } catch (Exception e) {
178                LOG.error("Parse Exception", e);
179            }
180            rulesToSave.put(rule.getId(), rule);
181            RuleBaseValues oldRule = rule.getPreviousVersion();
182            if (oldRule != null) {
183                performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
184
185                oldRule.setCurrentInd(Boolean.FALSE);
186                oldRule.setDeactivationDate(date);
187                rulesToSave.put(oldRule.getId(), oldRule);
188                if (!delegateFirst) {
189                    responsibilityIds.addAll(getResponsibilityIdsFromGraph(oldRule, isGenerateRuleArs));
190                }
191                //TODO if more than one delegate is edited from the create delegation screen (which currently can not happen), then this logic will not work.
192                if (rule.getDelegateRule().booleanValue() && rule.getPreviousRuleId() != null) {
193                    delegateFirst = true;
194                }
195
196                List<RuleBaseValues> oldDelegationRules = findOldDelegationRules(oldRule, rule, performanceLogger);
197                for (RuleBaseValues delegationRule : oldDelegationRules) {
198
199                    performanceLogger.log("Setting previous delegation rule: " + delegationRule.getId() + "to non current.");
200
201                    delegationRule.setCurrentInd(Boolean.FALSE);
202                    rulesToSave.put(delegationRule.getId(), delegationRule);
203                    responsibilityIds.addAll(getResponsibilityIdsFromGraph(delegationRule, isGenerateRuleArs));
204                }
205            }
206            for (Object element : rule.getRuleResponsibilities()) {
207                RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
208                for (Object element2 : responsibility.getDelegationRules()) {
209                    RuleDelegationBo delegation = (RuleDelegationBo) element2;
210
211                    delegation.getDelegationRule().setCurrentInd(Boolean.TRUE);
212                    RuleBaseValues delegatorRule = delegation.getDelegationRule();
213
214                    performanceLogger.log("Setting delegate rule: " + delegatorRule.getDescription() + " to current.");
215                    if (delegatorRule.getActivationDate() == null) {
216                        delegatorRule.setActivationDate(date);
217                    }
218                    try {
219                        delegatorRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
220                    } catch (Exception e) {
221                        LOG.error("Parse Exception", e);
222                    }
223                    rulesToSave.put(delegatorRule.getId(), delegatorRule);
224                }
225            }
226        }
227        for (RuleBaseValues rule : rulesToSave.values()) {
228            getRuleDAO().save(rule);
229            performanceLogger.log("Saved rule: " + rule.getId());
230        }
231        getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
232        performanceLogger.log("Time to make current");
233    }
234
235    /**
236     * TODO consolidate this method with makeCurrent.  The reason there's a seperate implementation is because the
237     * original makeCurrent(...) could not properly handle versioning a List of multiple rules (including multiple
238     * delegates rules for a single parent.  ALso, this work is being done for a patch so we want to mitigate the
239     * impact on the existing rule code.
240     *
241     * <p>This version will only work for remove/replace operations where rules
242     * aren't being added or removed.  This is why it doesn't perform some of the functions like checking
243     * for delegation rules that were removed from a parent rule.
244     */
245    public void makeCurrent2(List<RuleBaseValues> rules) {
246        PerformanceLogger performanceLogger = new PerformanceLogger();
247
248        boolean isGenerateRuleArs = true;
249        String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
250        if (!StringUtils.isBlank(generateRuleArs)) {
251            isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
252        }
253        Set<String> responsibilityIds = new HashSet<String>();
254        Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
255
256        Collections.sort(rules, new RuleDelegationSorter());
257        for (RuleBaseValues rule : rules) {
258            performanceLogger.log("Preparing rule: " + rule.getDescription());
259
260            rule.setCurrentInd(Boolean.TRUE);
261            Timestamp date = new Timestamp(System.currentTimeMillis());
262            rule.setActivationDate(date);
263            try {
264                rule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
265            } catch (Exception e) {
266                LOG.error("Parse Exception", e);
267            }
268            rulesToSave.put(rule.getId(), rule);
269            RuleBaseValues oldRule = rule.getPreviousVersion();
270            if (oldRule != null) {
271                performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
272                oldRule.setCurrentInd(Boolean.FALSE);
273                oldRule.setDeactivationDate(date);
274                rulesToSave.put(oldRule.getId(), oldRule);
275                responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
276            }
277            for (Object element : rule.getRuleResponsibilities()) {
278                RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
279                for (Object element2 : responsibility.getDelegationRules()) {
280                    RuleDelegationBo delegation = (RuleDelegationBo) element2;
281                    RuleBaseValues delegateRule = delegation.getDelegationRule();
282                    delegateRule.setCurrentInd(Boolean.TRUE);
283                    performanceLogger.log("Setting delegate rule: " + delegateRule.getDescription() + " to current.");
284                    if (delegateRule.getActivationDate() == null) {
285                        delegateRule.setActivationDate(date);
286                    }
287                    try {
288                        delegateRule.setDeactivationDate(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100").getTime()));
289                    } catch (Exception e) {
290                        LOG.error("Parse Exception", e);
291                    }
292                    rulesToSave.put(delegateRule.getId(), delegateRule);
293                }
294            }
295        }
296        for (RuleBaseValues rule : rulesToSave.values()) {
297            getRuleDAO().save(rule);
298            performanceLogger.log("Saved rule: " + rule.getId());
299
300        }
301
302        if (isGenerateRuleArs) {
303            getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
304        }
305        performanceLogger.log("Time to make current");
306    }
307
308    /**
309     * makeCurrent(RuleBaseValues) is the version of makeCurrent which is initiated from the new Routing Rule
310     * Maintenance document.  Because of the changes in the data model and the front end here,
311     * this method can be much less complicated than the previous 2!
312     */
313    public void makeCurrent(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
314        makeCurrent(null, rule, isRetroactiveUpdatePermitted);
315    }
316
317    public void makeCurrent(RuleDelegationBo ruleDelegation, boolean isRetroactiveUpdatePermitted) {
318        clearCache(RuleDelegation.Cache.NAME);
319        makeCurrent(ruleDelegation, ruleDelegation.getDelegationRule(), isRetroactiveUpdatePermitted);
320    }
321
322    protected void makeCurrent(RuleDelegationBo ruleDelegation, RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
323        PerformanceLogger performanceLogger = new PerformanceLogger();
324
325        boolean isGenerateRuleArs = false;
326        if (isRetroactiveUpdatePermitted) {
327                isGenerateRuleArs = true;
328                String generateRuleArs = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_GENERATE_ACTION_REQESTS_IND);
329                if (!StringUtils.isBlank(generateRuleArs)) {
330                        isGenerateRuleArs = KewApiConstants.YES_RULE_CHANGE_AR_GENERATION_VALUE.equalsIgnoreCase(generateRuleArs);
331                }
332        }
333        Set<String> responsibilityIds = new HashSet<String>();
334
335
336        performanceLogger.log("Preparing rule: " + rule.getDescription());
337
338        Map<String, RuleBaseValues> rulesToSave = new HashMap<String, RuleBaseValues>();
339        generateRuleNameIfNeeded(rule);
340        assignResponsibilityIds(rule);
341        rule.setCurrentInd(Boolean.TRUE);
342        Timestamp date = new Timestamp(System.currentTimeMillis());
343        rule.setActivationDate(date);
344        rule.setDeactivationDate(null);
345
346        rulesToSave.put(rule.getId(), rule);
347        if (rule.getPreviousRuleId() != null) {
348                RuleBaseValues oldRule = findRuleBaseValuesById(rule.getPreviousRuleId());
349                rule.setPreviousVersion(oldRule);
350        }
351        rule.setVersionNbr(0);
352        rule.setObjectId(null);
353        RuleBaseValues oldRule = rule.getPreviousVersion();
354        if (oldRule != null) {
355                performanceLogger.log("Setting previous rule: " + oldRule.getId() + " to non current.");
356                oldRule.setCurrentInd(Boolean.FALSE);
357                oldRule.setDeactivationDate(date);
358                rulesToSave.put(oldRule.getId(), oldRule);
359                responsibilityIds.addAll(getModifiedResponsibilityIds(oldRule, rule));
360                rule.setVersionNbr(getNextVersionNumber(oldRule));
361        }
362               
363
364        boolean isRuleDelegation = ruleDelegation != null;
365        
366        for (RuleBaseValues ruleToSave : rulesToSave.values()) {
367                getRuleDAO().save(ruleToSave);
368                performanceLogger.log("Saved rule: " + ruleToSave.getId());
369        }
370        if (isRuleDelegation) {
371                responsibilityIds.add(ruleDelegation.getResponsibilityId());
372                ruleDelegation.setDelegateRuleId(rule.getId());
373                getRuleDelegationService().save(ruleDelegation);
374        }
375        
376        if (isGenerateRuleArs
377                && org.apache.commons.collections.CollectionUtils.isNotEmpty(responsibilityIds)) {
378            getActionRequestService().updateActionRequestsForResponsibilityChange(responsibilityIds);
379        }
380        performanceLogger.log("Time to make current");
381    }
382    
383    private void clearCache(String cacheName) {
384        DistributedCacheManagerDecorator distributedCacheManagerDecorator =
385                GlobalResourceLoader.getService(KewImplConstants.KEW_DISTRIBUTED_CACHE_MANAGER);
386
387        Cache cache = distributedCacheManagerDecorator.getCache(cacheName);
388        if (cache != null) {
389            cache.clear();
390        }
391    }
392
393    public RuleBaseValues getParentRule(String ruleBaseValuesId) {
394        return getRuleDAO().getParentRule(ruleBaseValuesId);
395    }
396
397    private Set getResponsibilityIdsFromGraph(RuleBaseValues rule, boolean isRuleCollecting) {
398        Set responsibilityIds = new HashSet();
399        for (Object element : rule.getRuleResponsibilities()) {
400            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
401            if (isRuleCollecting) {
402                responsibilityIds.add(responsibility.getResponsibilityId());
403            }
404        }
405        return responsibilityIds;
406    }
407
408    /**
409     * Returns the responsibility IDs that were modified between the 2 given versions of the rule.  Any added
410     * or removed responsibilities are also included in the returned Set.
411     */
412    private Set<String> getModifiedResponsibilityIds(RuleBaseValues oldRule, RuleBaseValues newRule) {
413        Map<String, RuleResponsibilityBo> modifiedResponsibilityMap = new HashMap<String, RuleResponsibilityBo>();
414        for (Object element : oldRule.getRuleResponsibilities()) {
415            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
416            modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
417        }
418        for (Object element : newRule.getRuleResponsibilities()) {
419            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
420            RuleResponsibilityBo oldResponsibility = modifiedResponsibilityMap.get(responsibility.getResponsibilityId());
421            if (oldResponsibility == null) {
422                // if there's no old responsibility then it's a new responsibility, add it
423                modifiedResponsibilityMap.put(responsibility.getResponsibilityId(), responsibility);
424            } else if (!hasResponsibilityChanged(oldResponsibility, responsibility)) {
425                // if it hasn't been modified, remove it from the collection of modified ids
426                modifiedResponsibilityMap.remove(responsibility.getResponsibilityId());
427            }
428        }
429        return modifiedResponsibilityMap.keySet();
430    }
431
432    /**
433     * Determines if the given responsibilities are different or not.
434     */
435    private boolean hasResponsibilityChanged(RuleResponsibilityBo oldResponsibility, RuleResponsibilityBo newResponsibility) {
436        return !ObjectUtils.equals(oldResponsibility.getActionRequestedCd(), newResponsibility.getActionRequestedCd()) ||
437        !ObjectUtils.equals(oldResponsibility.getApprovePolicy(), newResponsibility.getActionRequestedCd()) ||
438        !ObjectUtils.equals(oldResponsibility.getPriority(), newResponsibility.getPriority()) ||
439        !ObjectUtils.equals(oldResponsibility.getRole(), newResponsibility.getRole()) ||
440        !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityName(), newResponsibility.getRuleResponsibilityName()) ||
441        !ObjectUtils.equals(oldResponsibility.getRuleResponsibilityType(), newResponsibility.getRuleResponsibilityType());
442    }
443
444    /**
445     * This method will find any old delegation rules on the previous version of the parent rule which are not on the
446     * new version of the rule so that they can be marked non-current.
447     */
448    private List<RuleBaseValues> findOldDelegationRules(RuleBaseValues oldRule, RuleBaseValues newRule, PerformanceLogger performanceLogger) {
449        performanceLogger.log("Begin to get delegation rules.");
450        List<RuleBaseValues> oldDelegations = getRuleDAO().findOldDelegations(oldRule, newRule);
451        performanceLogger.log("Located "+oldDelegations.size()+" old delegation rules.");
452        return oldDelegations;
453    }
454
455    public String routeRuleWithDelegate(String documentId, RuleBaseValues parentRule, RuleBaseValues delegateRule, PrincipalContract principal, String annotation, boolean blanketApprove) throws Exception {
456        if (parentRule == null) {
457            throw new IllegalArgumentException("Cannot route a delegate without a parent rule.");
458        }
459        if (parentRule.getDelegateRule().booleanValue()) {
460            throw new IllegalArgumentException("Parent rule cannot be a delegate.");
461        }
462        if (parentRule.getPreviousRuleId() == null && delegateRule.getPreviousRuleId() == null) {
463            throw new IllegalArgumentException("Previous rule version required.");
464        }
465
466        // if the parent rule is new, unsaved, then save it
467//      boolean isRoutingParent = parentRule.getId() == null;
468//      if (isRoutingParent) {
469//      // it's very important that we do not save delegations here (that's what the false parameter is for)
470//      // this is because, if we save the delegations, the existing delegations on our parent rule will become
471//      // saved as "non current" before the rule is approved!!!
472//      save2(parentRule, null, false);
473//      //save2(parentRule, null, true);
474//      }
475
476        // XXX: added when the RuleValidation stuff was added, basically we just need to get the RuleDelegation
477        // that points to our delegate rule, this rule code is scary...
478        RuleDelegationBo ruleDelegation = getRuleDelegation(parentRule, delegateRule);
479
480        save2(delegateRule, ruleDelegation, true);
481
482//      if the parent rule is new, unsaved, then save it
483        // It's important to save the parent rule after the delegate rule is saved, that way we can ensure that any new rule
484        // delegations have a valid, saved, delegation rule to point to (otherwise we end up with a null constraint violation)
485        boolean isRoutingParent = parentRule.getId() == null;
486        if (isRoutingParent) {
487            // it's very important that we do not save delegations here (that's what the false parameter is for)
488            // this is because, if we save the delegations, the existing delegations on our parent rule will become
489            // saved as "non current" before the rule is approved!!!
490            save2(parentRule, null, false);
491            //save2(parentRule, null, true);
492        }
493
494        WorkflowDocument workflowDocument = null;
495        if (documentId != null) {
496            workflowDocument = WorkflowDocumentFactory.loadDocument(principal.getPrincipalId(), documentId);
497        } else {
498            List rules = new ArrayList();
499            rules.add(delegateRule);
500            rules.add(parentRule);
501            workflowDocument = WorkflowDocumentFactory.createDocument(principal.getPrincipalId(), getRuleDocumentTypeName(
502                    rules));
503        }
504        workflowDocument.setTitle(generateTitle(parentRule, delegateRule));
505        delegateRule.setDocumentId(workflowDocument.getDocumentId());
506        workflowDocument.addAttributeDefinition(RuleRoutingDefinition.createAttributeDefinition(parentRule.getDocTypeName()));
507        getRuleDAO().save(delegateRule);
508        if (isRoutingParent) {
509            parentRule.setDocumentId(workflowDocument.getDocumentId());
510            getRuleDAO().save(parentRule);
511        }
512        if (blanketApprove) {
513            workflowDocument.blanketApprove(annotation);
514        } else {
515            workflowDocument.route(annotation);
516        }
517        return workflowDocument.getDocumentId();
518    }
519
520    /**
521     * Gets the RuleDelegation object from the parentRule that points to the delegateRule.
522     */
523    private RuleDelegationBo getRuleDelegation(RuleBaseValues parentRule, RuleBaseValues delegateRule) throws Exception {
524        for (Object element : parentRule.getRuleResponsibilities()) {
525            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
526            for (Object element2 : responsibility.getDelegationRules()) {
527                RuleDelegationBo ruleDelegation = (RuleDelegationBo) element2;
528                // they should be the same object in memory
529                if (ruleDelegation.getDelegationRule().equals(delegateRule)) {
530                    return ruleDelegation;
531                }
532            }
533        }
534        return null;
535    }
536
537    private String generateTitle(RuleBaseValues parentRule, RuleBaseValues delegateRule) {
538        StringBuffer title = new StringBuffer();
539        if (delegateRule.getPreviousRuleId() != null) {
540            title.append("Editing Delegation Rule '").append(delegateRule.getDescription()).append("' on '");
541        } else {
542            title.append("Adding Delegation Rule '").append(delegateRule.getDescription()).append("' to '");
543        }
544        title.append(parentRule.getDescription()).append("'");
545        return title.toString();
546    }
547
548    public void validate(RuleBaseValues ruleBaseValues, List errors) {
549        if (errors == null) {
550            errors = new ArrayList();
551        }
552        if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
553            errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
554        }
555        if (ruleBaseValues.getToDateValue().before(ruleBaseValues.getFromDateValue())) {
556            errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
557        }
558        if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
559            errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
560        }
561        if (ruleBaseValues.getRuleResponsibilities().isEmpty()) {
562            errors.add(new WorkflowServiceErrorImpl("A responsibility is required", "routetemplate.ruleservice.responsibility.required"));
563        } else {
564            for (Object element : ruleBaseValues.getRuleResponsibilities()) {
565                RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
566                if (responsibility.getRuleResponsibilityName() != null && KewApiConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
567                    if (getGroupService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
568                        errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
569                    }
570                } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
571                    errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
572                }
573            }
574        }
575        if (!errors.isEmpty()) {
576            throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
577        }
578    }
579
580    public void validate2(RuleBaseValues ruleBaseValues, RuleDelegationBo ruleDelegation, List errors) {
581        if (errors == null) {
582            errors = new ArrayList();
583        }
584        if (getDocumentTypeService().findByName(ruleBaseValues.getDocTypeName()) == null) {
585            errors.add(new WorkflowServiceErrorImpl("Document Type Invalid", "doctype.documenttypeservice.doctypename.required"));
586            LOG.error("Document Type Invalid");
587        }
588        if (ruleBaseValues.getToDateValue() == null) {
589            try {
590                ruleBaseValues.setToDateValue(new Timestamp(RiceConstants.getDefaultDateFormat().parse("01/01/2100")
591                        .getTime()));
592            } catch (ParseException e) {
593                LOG.error("Error date-parsing default date");
594                throw new WorkflowServiceErrorException("Error parsing default date.", e);
595            }
596        }
597        if (ruleBaseValues.getFromDateValue() == null) {
598            ruleBaseValues.setFromDateValue(new Timestamp(System.currentTimeMillis()));
599        }
600        if (ruleBaseValues.getToDateValue().before(ruleBaseValues.getFromDateValue())) {
601            errors.add(new WorkflowServiceErrorImpl("From Date is later than to date", "routetemplate.ruleservice.daterange.fromafterto"));
602            LOG.error("From Date is later than to date");
603        }
604        if (ruleBaseValues.getDescription() == null || ruleBaseValues.getDescription().equals("")) {
605            errors.add(new WorkflowServiceErrorImpl("Description is required", "routetemplate.ruleservice.description.required"));
606            LOG.error("Description is missing");
607        }
608
609        for (Object element : ruleBaseValues.getRuleResponsibilities()) {
610            RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
611            if (responsibility.getRuleResponsibilityName() != null && KewApiConstants.RULE_RESPONSIBILITY_GROUP_ID.equals(responsibility.getRuleResponsibilityType())) {
612                if (getGroupService().getGroup(responsibility.getRuleResponsibilityName()) == null) {
613                    errors.add(new WorkflowServiceErrorImpl("Workgroup is invalid", "routetemplate.ruleservice.workgroup.invalid"));
614                    LOG.error("Workgroup is invalid");
615                }
616            } else if (responsibility.getPrincipal() == null && responsibility.getRole() == null) {
617                errors.add(new WorkflowServiceErrorImpl("User is invalid", "routetemplate.ruleservice.user.invalid"));
618                LOG.error("User is invalid");
619            } else if (responsibility.isUsingRole()) {
620                if (responsibility.getApprovePolicy() == null || !(responsibility.getApprovePolicy().equals(ActionRequestPolicy.ALL.getCode()) || responsibility.getApprovePolicy().equals(ActionRequestPolicy.FIRST.getCode()))) {
621                    errors.add(new WorkflowServiceErrorImpl("Approve Policy is Invalid", "routetemplate.ruleservice.approve.policy.invalid"));
622                    LOG.error("Approve Policy is Invalid");
623                }
624            }
625        }
626
627        if (ruleBaseValues.getRuleTemplate() != null) {
628            for (Object element : ruleBaseValues.getRuleTemplate().getActiveRuleTemplateAttributes()) {
629                RuleTemplateAttributeBo templateAttribute = (RuleTemplateAttributeBo) element;
630                if (!templateAttribute.isRuleValidationAttribute()) {
631                    continue;
632                }
633                RuleValidationAttribute attribute = templateAttribute.getRuleValidationAttribute();
634                UserSession userSession = GlobalVariables.getUserSession();
635                try {
636                    RuleValidationContext validationContext = RuleValidationContext.Builder.create(RuleBaseValues.to(ruleBaseValues), RuleDelegationBo
637                            .to(ruleDelegation), userSession.getPrincipalId()).build();
638                    ValidationResults results = attribute.validate(validationContext);
639                    if (results != null && !results.getErrors().isEmpty()) {
640                        errors.add(results);
641                    }
642                } catch (Exception e) {
643                    if (e instanceof RuntimeException) {
644                        throw (RuntimeException)e;
645                    }
646                    throw new RuntimeException("Problem validation rule.", e);
647                }
648
649            }
650        }
651        if (ruleBaseValues.getRuleExpressionDef() != null) {
652            // rule expressions do not require parse-/save-time validation
653        }
654
655        if (!errors.isEmpty()) {
656            throw new WorkflowServiceErrorException("RuleBaseValues validation errors", errors);
657        }
658    }
659
660    public List<RuleBaseValues> findByDocumentId(String documentId) {
661        return getRuleDAO().findByDocumentId(documentId);
662    }
663
664    public List<RuleBaseValues> search(String docTypeName, String ruleId, String ruleTemplateId, String ruleDescription, String groupId, String principalId,
665            Boolean delegateRule, Boolean activeInd, Map extensionValues, String workflowIdDirective) {
666        return getRuleDAO().search(docTypeName, ruleId, ruleTemplateId, ruleDescription, groupId, principalId, delegateRule,
667                activeInd, extensionValues, workflowIdDirective);
668    }
669
670    public List<RuleBaseValues> searchByTemplate(String docTypeName, String ruleTemplateName, String ruleDescription, String groupId, String principalId,
671            Boolean workgroupMember, Boolean delegateRule, Boolean activeInd, Map extensionValues, Collection<String> actionRequestCodes) {
672
673        if ( (StringUtils.isEmpty(docTypeName)) &&
674                (StringUtils.isEmpty(ruleTemplateName)) &&
675                (StringUtils.isEmpty(ruleDescription)) &&
676                (StringUtils.isEmpty(groupId)) &&
677                (StringUtils.isEmpty(principalId)) &&
678                (extensionValues.isEmpty()) &&
679                (actionRequestCodes.isEmpty()) ) {
680            // all fields are empty
681            throw new IllegalArgumentException("At least one criterion must be sent");
682        }
683
684        RuleTemplateBo ruleTemplate = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName);
685        String ruleTemplateId = null;
686        if (ruleTemplate != null) {
687            ruleTemplateId = ruleTemplate.getId();
688        }
689
690        if ( ( (extensionValues != null) && (!extensionValues.isEmpty()) ) &&
691                (ruleTemplateId == null) ) {
692            // cannot have extensions without a correct template
693            throw new IllegalArgumentException("A Rule Template Name must be given if using Rule Extension values");
694        }
695
696        Collection<String> workgroupIds = new ArrayList<String>();
697        if (principalId != null) {
698            KEWServiceLocator.getIdentityHelperService().validatePrincipalId(principalId);
699            if ( (workgroupMember == null) || (workgroupMember.booleanValue()) ) {
700                        workgroupIds = getGroupService().getGroupIdsByPrincipalId(principalId);
701                } else {
702                        // user was passed but workgroups should not be parsed... do nothing
703                }
704        } else if (groupId != null) {
705                Group group = KEWServiceLocator.getIdentityHelperService().getGroup(groupId);
706                if (group == null) {
707                        throw new IllegalArgumentException("Group does not exist in for given group id: " + groupId);
708                } else  {
709                        workgroupIds.add(group.getId());
710                }
711        }
712
713        return getRuleDAO().search(docTypeName, ruleTemplateId, ruleDescription, workgroupIds, principalId,
714                delegateRule,activeInd, extensionValues, actionRequestCodes);
715    }
716
717    public void delete(String ruleBaseValuesId) {
718        getRuleDAO().delete(ruleBaseValuesId);
719    }
720
721    public RuleBaseValues findRuleBaseValuesById(String ruleBaseValuesId) {
722        return getRuleDAO().findRuleBaseValuesById(ruleBaseValuesId);
723    }
724
725    public RuleResponsibilityBo findRuleResponsibility(String responsibilityId) {
726        return getRuleDAO().findRuleResponsibility(responsibilityId);
727    }
728
729    public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType) {
730                String ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getId();
731            return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType));
732    }
733
734    public List fetchAllCurrentRulesForTemplateDocCombination(String ruleTemplateName, String documentType, Timestamp effectiveDate){
735        String ruleTemplateId = getRuleTemplateService().findByRuleTemplateName(ruleTemplateName).getId();
736        PerformanceLogger performanceLogger = new PerformanceLogger();
737        performanceLogger.log("Time to fetchRules by template " + ruleTemplateName + " not caching.");
738        return getRuleDAO().fetchAllCurrentRulesForTemplateDocCombination(ruleTemplateId, getDocGroupAndTypeList(documentType), effectiveDate);
739    }
740    public List fetchAllRules(boolean currentRules) {
741        return getRuleDAO().fetchAllRules(currentRules);
742    }
743
744    private List getDocGroupAndTypeList(String documentType) {
745        List docTypeList = new ArrayList();
746        DocumentTypeService docTypeService = (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
747        DocumentType docType = docTypeService.findByName(documentType);
748        while (docType != null) {
749            docTypeList.add(docType.getName());
750            docType = docType.getParentDocType();
751        }
752        return docTypeList;
753    }
754
755    private Integer getNextVersionNumber(RuleBaseValues currentRule) {
756        List candidates = new ArrayList();
757        candidates.add(currentRule.getVersionNbr());
758        List pendingRules = ruleDAO.findByPreviousRuleId(currentRule.getId());
759        for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
760            RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
761            candidates.add(pendingRule.getVersionNbr());
762        }
763        Collections.sort(candidates);
764        Integer maxVersionNumber = (Integer) candidates.get(candidates.size() - 1);
765        if (maxVersionNumber == null) {
766            return Integer.valueOf(0);
767        }
768        return Integer.valueOf(maxVersionNumber.intValue() + 1);
769    }
770
771    /**
772     * Determines if the given rule is locked for routing.
773     *
774     * In the case of a root rule edit, this method will take the rule id of the rule being edited.
775     *
776     * In the case of a new delegate rule or a delegate rule edit, this method will take the id of it's parent.
777     */
778    public String isLockedForRouting(String currentRuleBaseValuesId) {
779        // checks for any other versions of the given rule, essentially, if this is a rule edit we want to see how many other
780        // pending edits are out there
781        List pendingRules = ruleDAO.findByPreviousRuleId(currentRuleBaseValuesId);
782        boolean isDead = true;
783        for (Iterator iterator = pendingRules.iterator(); iterator.hasNext();) {
784            RuleBaseValues pendingRule = (RuleBaseValues) iterator.next();
785
786            if (pendingRule.getDocumentId() != null && StringUtils.isNotBlank(pendingRule.getDocumentId())) {
787                DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingRule.getDocumentId());
788                // the pending edit is considered dead if it's been disapproved or cancelled and we are allowed to proceed with our own edit
789                isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
790                if (!isDead) {
791                    return pendingRule.getDocumentId();
792                }
793            }
794
795            for (Object element : pendingRule.getRuleResponsibilities()) {
796                RuleResponsibilityBo responsibility = (RuleResponsibilityBo) element;
797                for (Object element2 : responsibility.getDelegationRules()) {
798                    RuleDelegationBo delegation = (RuleDelegationBo) element2;
799                    List pendingDelegateRules = ruleDAO.findByPreviousRuleId(delegation.getDelegationRule().getId());
800                    for (Iterator iterator3 = pendingDelegateRules.iterator(); iterator3.hasNext();) {
801                        RuleBaseValues pendingDelegateRule = (RuleBaseValues) iterator3.next();
802                        if (pendingDelegateRule.getDocumentId() != null && StringUtils.isNotBlank(pendingDelegateRule.getDocumentId())) {
803                            DocumentRouteHeaderValue routeHeader = getRouteHeaderService().getRouteHeader(pendingDelegateRule.getDocumentId());
804                            isDead = routeHeader.isDisaproved() || routeHeader.isCanceled();
805                            if (!isDead) {
806                                return pendingDelegateRule.getDocumentId();
807                            }
808                        }
809                    }
810                }
811            }
812        }
813        return null;
814    }
815
816    public RuleBaseValues getParentRule(RuleBaseValues rule) {
817        if (rule == null || rule.getId() == null) {
818            throw new IllegalArgumentException("Rule must be non-null with non-null id: " + rule);
819        }
820        if (!Boolean.TRUE.equals(rule.getDelegateRule())) {
821            return null;
822        }
823        return getRuleDAO().getParentRule(rule.getId());
824    }
825
826    /**
827     * This configuration is currently stored in a system parameter named "CUSTOM_DOCUMENT_TYPES ",
828     * long term we should come up with a better solution.  The format of this constant is a comma-separated
829     * list of entries of the following form:
830     *
831     * <<name of doc type on rule>>:<<rule template name on rule>>:<<type of rule>>:<<name of document type to use for rule routing>>
832     *
833     * Rule type indicates either main or delegation rules.  A main rule is indicated by the character 'M' and a
834     * delegate rule is indicated by the character 'D'.
835     *
836     * So, if you wanted to route "main" rules made for the "MyDocType" document with the rule template name
837     * "MyRuleTemplate" using the "MyMainRuleDocType" doc type, it would be specified as follows:
838     *
839     * MyDocType:MyRuleTemplate:M:MyMainRuleDocType
840     *
841     * If you also wanted to route "delegate" rules made for the "MyDocType" document with rule template name
842     * "MyDelegateTemplate" using the "MyDelegateRuleDocType", you would then set the constant as follows:
843     *
844     * MyDocType:MyRuleTemplate:M:MyMainRuleDocType,MyDocType:MyDelegateTemplate:D:MyDelegateRuleDocType
845     *
846     * TODO this method ended up being a mess, we should get rid of this as soon as we can
847     */
848    public String getRuleDocumentTypeName(List rules) {
849        if (rules.size() == 0) {
850            throw new IllegalArgumentException("Cannot determine rule DocumentType for an empty list of rules.");
851        }
852        String ruleDocTypeName = null;
853        RuleRoutingConfig config = RuleRoutingConfig.parse();
854        // There are 2 cases here
855        RuleBaseValues firstRule = (RuleBaseValues)rules.get(0);
856        if (Boolean.TRUE.equals(firstRule.getDelegateRule())) {
857            // if it's a delegate rule then the list will contain only 2 elements, the first is the delegate rule,
858            // the second is the parent rule.  In this case just look at the custom routing process for the delegate rule.
859            ruleDocTypeName = config.getDocumentTypeName(firstRule);
860        } else {
861            // if this is a list of parent rules being routed, look at all configued routing types and verify that they are
862            // all the same, if not throw an exception
863            String parentRulesDocTypeName = null;
864            for (Iterator iterator = rules.iterator(); iterator.hasNext();) {
865                RuleBaseValues rule = (RuleBaseValues) iterator.next();
866                // if it's a delegate rule just skip it
867                if  (Boolean.TRUE.equals(rule.getDelegateRule())) {
868                    continue;
869                }
870                String currentDocTypeName = config.getDocumentTypeName(rule);
871                if (parentRulesDocTypeName == null) {
872                    parentRulesDocTypeName = currentDocTypeName;
873                } else {
874                    if (!ObjectUtils.equals(currentDocTypeName, parentRulesDocTypeName)) {
875                        throw new RuntimeException("There are multiple rules being routed and they have different document type definitions!  " + parentRulesDocTypeName + " and " + currentDocTypeName);
876                    }
877                }
878            }
879            ruleDocTypeName = parentRulesDocTypeName;
880        }
881        if (ruleDocTypeName == null) {
882            ruleDocTypeName = KewApiConstants.DEFAULT_RULE_DOCUMENT_NAME;
883        }
884        return ruleDocTypeName;
885    }
886
887    public void setRuleDAO(RuleDAO ruleDAO) {
888        this.ruleDAO = ruleDAO;
889    }
890
891    public RuleDAO getRuleDAO() {
892        return ruleDAO;
893    }
894
895    public void deleteRuleResponsibilityById(String ruleResponsibilityId) {
896        getRuleResponsibilityDAO().delete(ruleResponsibilityId);
897    }
898
899    public RuleResponsibilityBo findByRuleResponsibilityId(String ruleResponsibilityId) {
900        return getRuleResponsibilityDAO().findByRuleResponsibilityId(ruleResponsibilityId);
901    }
902
903    public List findRuleBaseValuesByResponsibilityReviewer(String reviewerName, String type) {
904        return getRuleDAO().findRuleBaseValuesByResponsibilityReviewer(reviewerName, type);
905    }
906
907    public List findRuleBaseValuesByResponsibilityReviewerTemplateDoc(String ruleTemplateName, String documentType, String reviewerName, String type) {
908        return getRuleDAO().findRuleBaseValuesByResponsibilityReviewerTemplateDoc(ruleTemplateName, documentType, reviewerName, type);
909    }
910
911    public RuleTemplateService getRuleTemplateService() {
912        return (RuleTemplateService) KEWServiceLocator.getService(KEWServiceLocator.RULE_TEMPLATE_SERVICE);
913    }
914
915    public DocumentTypeService getDocumentTypeService() {
916        return (DocumentTypeService) KEWServiceLocator.getService(KEWServiceLocator.DOCUMENT_TYPE_SERVICE);
917    }
918
919    public GroupService getGroupService() {
920        return KimApiServiceLocator.getGroupService();
921    }
922
923    public ActionRequestService getActionRequestService() {
924        return (ActionRequestService) KEWServiceLocator.getService(KEWServiceLocator.ACTION_REQUEST_SRV);
925    }
926
927    private ResponsibilityIdService getResponsibilityIdService() {
928        return (ResponsibilityIdService) KEWServiceLocator.getService(KEWServiceLocator.RESPONSIBILITY_ID_SERVICE);
929    }
930
931    private RuleDelegationService getRuleDelegationService() {
932        return (RuleDelegationService) KEWServiceLocator.getService(KEWServiceLocator.RULE_DELEGATION_SERVICE);
933    }
934
935    private RouteHeaderService getRouteHeaderService() {
936        return (RouteHeaderService) KEWServiceLocator.getService(KEWServiceLocator.DOC_ROUTE_HEADER_SRV);
937    }
938
939    /**
940     * A comparator implementation which compares RuleBaseValues and puts all delegate rules first.
941     */
942    public class RuleDelegationSorter implements Comparator {
943        public int compare(Object arg0, Object arg1) {
944            RuleBaseValues rule1 = (RuleBaseValues) arg0;
945            RuleBaseValues rule2 = (RuleBaseValues) arg1;
946
947            Integer rule1Value = new Integer((rule1.getDelegateRule().booleanValue() ? 0 : 1));
948            Integer rule2Value = new Integer((rule2.getDelegateRule().booleanValue() ? 0 : 1));
949            int value = rule1Value.compareTo(rule2Value);
950            return value;
951        }
952    }
953
954
955    public void loadXml(InputStream inputStream, String principalId) {
956        RuleXmlParser parser = new RuleXmlParser();
957        try {
958            parser.parseRules(inputStream);
959        } catch (Exception e) { //any other exception
960            LOG.error("Error loading xml file", e);
961            WorkflowServiceErrorException wsee = new WorkflowServiceErrorException("Error loading xml file", new WorkflowServiceErrorImpl("Error loading xml file", XML_PARSE_ERROR));
962            wsee.initCause(e);
963            throw wsee;
964        }
965    }
966
967    public Element export(ExportDataSet dataSet) {
968        RuleXmlExporter exporter = new RuleXmlExporter(XmlConstants.RULE_NAMESPACE);
969        return exporter.export(dataSet);
970    }
971    
972    @Override
973        public boolean supportPrettyPrint() {
974                return true;
975        }
976
977    protected List<RuleBaseValues> loadRules(List<String> ruleIds) {
978        List<RuleBaseValues> rules = new ArrayList<RuleBaseValues>();
979        for (String ruleId : ruleIds) {
980            RuleBaseValues rule = KEWServiceLocator.getRuleService().findRuleBaseValuesById(ruleId);
981            rules.add(rule);
982        }
983        return rules;
984    }
985
986    /**
987     * If a rule has been modified and is no longer current since the original request was made, we need to
988     * be sure to NOT update the rule.
989     */
990    protected boolean shouldChangeRuleInvolvement(String documentId, RuleBaseValues rule) {
991        if (!rule.getCurrentInd()) {
992            LOG.warn("Rule requested for rule involvement change by document " + documentId + " is no longer current.  Change will not be executed!  Rule id is: " + rule.getId());
993            return false;
994        }
995        String lockingDocumentId = KEWServiceLocator.getRuleService().isLockedForRouting(rule.getId());
996        if (lockingDocumentId != null) {
997            LOG.warn("Rule requested for rule involvement change by document " + documentId + " is locked by document " + lockingDocumentId + " and cannot be modified.  " +
998                    "Change will not be executed!  Rule id is: " + rule.getId());
999            return false;
1000        }
1001        return true;
1002    }
1003
1004    protected RuleDelegationBo getRuleDelegationForDelegateRule(RuleBaseValues rule) {
1005        if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1006            List delegations = getRuleDelegationService().findByDelegateRuleId(rule.getId());
1007            for (Iterator iterator = delegations.iterator(); iterator.hasNext();) {
1008                RuleDelegationBo ruleDelegation = (RuleDelegationBo) iterator.next();
1009                RuleBaseValues parentRule = ruleDelegation.getRuleResponsibility().getRuleBaseValues();
1010                if (Boolean.TRUE.equals(parentRule.getCurrentInd())) {
1011                    return ruleDelegation;
1012                }
1013            }
1014        }
1015        return null;
1016    }
1017
1018    protected void hookUpDelegateRuleToParentRule(RuleBaseValues newParentRule, RuleBaseValues newDelegationRule, RuleDelegationBo existingRuleDelegation) {
1019        // hook up parent rule to new rule delegation
1020        boolean foundDelegation = false;
1021        outer:for (RuleResponsibilityBo responsibility : (List<RuleResponsibilityBo>)newParentRule.getRuleResponsibilities()) {
1022            for (RuleDelegationBo ruleDelegation : (List<RuleDelegationBo>)responsibility.getDelegationRules()) {
1023                if (ruleDelegation.getDelegationRule().getId().equals(existingRuleDelegation.getDelegationRule().getId())) {
1024                    ruleDelegation.setDelegationRule(newDelegationRule);
1025                    foundDelegation = true;
1026                    break outer;
1027                }
1028            }
1029        }
1030        if (!foundDelegation) {
1031            throw new WorkflowRuntimeException("Failed to locate the existing rule delegation with id: " + existingRuleDelegation.getDelegationRule().getId());
1032        }
1033
1034    }
1035
1036    protected RuleBaseValues createNewRuleVersion(RuleBaseValues existingRule, String documentId) throws Exception {
1037        RuleBaseValues rule = new RuleBaseValues();
1038        PropertyUtils.copyProperties(rule, existingRule);
1039        rule.setPreviousVersion(existingRule);
1040        rule.setPreviousRuleId(existingRule.getId());
1041        rule.setId(null);
1042        rule.setActivationDate(null);
1043        rule.setDeactivationDate(null);
1044        rule.setVersionNumber(0L);
1045        rule.setDocumentId(documentId);
1046
1047        // TODO: FIXME: need to copy the rule expression here too?
1048
1049        rule.setRuleResponsibilities(new ArrayList());
1050        for (RuleResponsibilityBo existingResponsibility : (List<RuleResponsibilityBo>)existingRule.getRuleResponsibilities()) {
1051            RuleResponsibilityBo responsibility = new RuleResponsibilityBo();
1052            PropertyUtils.copyProperties(responsibility, existingResponsibility);
1053            responsibility.setRuleBaseValues(rule);
1054            responsibility.setRuleBaseValuesId(null);
1055            responsibility.setId(null);
1056            responsibility.setVersionNumber(0L);
1057            rule.getRuleResponsibilities().add(responsibility);
1058//            responsibility.setDelegationRules(new ArrayList());
1059//            for (RuleDelegation existingDelegation : (List<RuleDelegation>)existingResponsibility.getDelegationRules()) {
1060//                RuleDelegation delegation = new RuleDelegation();
1061//                PropertyUtils.copyProperties(delegation, existingDelegation);
1062//                delegation.setRuleDelegationId(null);
1063//                delegation.setRuleResponsibility(responsibility);
1064//                delegation.setRuleResponsibilityId(null);
1065//                delegation.setVersionNumber(0L);
1066//                // it's very important that we do NOT recurse down into the delegation rules and reversion those,
1067//                // this is important to how rule versioning works
1068//                responsibility.getDelegationRules().add(delegation);
1069//            }
1070        }
1071        rule.setRuleExtensions(new ArrayList());
1072        for (RuleExtensionBo existingExtension : (List<RuleExtensionBo>)existingRule.getRuleExtensions()) {
1073            RuleExtensionBo extension = new RuleExtensionBo();
1074            PropertyUtils.copyProperties(extension, existingExtension);
1075            extension.setVersionNumber(new Long(0));
1076            extension.setRuleBaseValues(rule);
1077            extension.setRuleBaseValuesId(null);
1078            extension.setRuleExtensionId(null);
1079            rule.getRuleExtensions().add(extension);
1080            extension.setExtensionValues(new ArrayList<RuleExtensionValue>());
1081            for (RuleExtensionValue existingExtensionValue : extension.getExtensionValues()) {
1082                RuleExtensionValue extensionValue = new RuleExtensionValue();
1083                PropertyUtils.copyProperties(extensionValue, existingExtensionValue);
1084                extensionValue.setExtension(extension);
1085                extensionValue.setRuleExtensionId(null);
1086                extensionValue.setLockVerNbr(0);
1087                extensionValue.setRuleExtensionValueId(null);
1088                extension.getExtensionValues().add(extensionValue);
1089            }
1090        }
1091        return rule;
1092    }
1093
1094    private static class RuleVersion {
1095        public RuleBaseValues rule;
1096        public RuleBaseValues parent;
1097        public RuleDelegationBo delegation;
1098    }
1099
1100    private static class RuleRoutingConfig {
1101        private List configs = new ArrayList();
1102        public static RuleRoutingConfig parse() {
1103            RuleRoutingConfig config = new RuleRoutingConfig();
1104            String constant = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsString(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.RULE_DETAIL_TYPE, KewApiConstants.RULE_CUSTOM_DOC_TYPES);
1105            if (!StringUtils.isEmpty(constant)) {
1106                String[] ruleConfigs = constant.split(",");
1107                for (String ruleConfig : ruleConfigs) {
1108                    String[] configElements = ruleConfig.split(":");
1109                    if (configElements.length != 4) {
1110                        throw new RuntimeException("Found incorrect number of config elements within a section of the custom rule document types config.  There should have been four ':' delimited sections!  " + ruleConfig);
1111                    }
1112                    config.configs.add(configElements);
1113                }
1114            }
1115            return config;
1116        }
1117        public String getDocumentTypeName(RuleBaseValues rule) {
1118            for (Iterator iterator = configs.iterator(); iterator.hasNext();) {
1119                String[] configElements = (String[]) iterator.next();
1120                String docTypeName = configElements[0];
1121                String ruleTemplateName = configElements[1];
1122                String type = configElements[2];
1123                String ruleDocTypeName = configElements[3];
1124                if (rule.getDocTypeName().equals(docTypeName) && rule.getRuleTemplateName().equals(ruleTemplateName)) {
1125                    if (type.equals("M")) {
1126                        if (Boolean.FALSE.equals(rule.getDelegateRule())) {
1127                            return ruleDocTypeName;
1128                        }
1129                    } else if (type.equals("D")) {
1130                        if (Boolean.TRUE.equals(rule.getDelegateRule())) {
1131                            return ruleDocTypeName;
1132                        }
1133                    } else {
1134                        throw new RuntimeException("Bad rule type '" + type + "' in rule doc type routing config.");
1135                    }
1136                }
1137            }
1138            return null;
1139        }
1140    }
1141
1142    public String getDuplicateRuleId(RuleBaseValues rule) {
1143
1144        // TODO: this method is extremely slow, if we could implement a more optimized query here, that would help tremendously
1145        Rule baseRule = RuleBaseValues.to(rule);
1146        List<RuleResponsibility> responsibilities = baseRule.getRuleResponsibilities();
1147        List<RuleExtension> extensions = baseRule.getRuleExtensions();
1148        String docTypeName = baseRule.getDocTypeName();
1149        String ruleTemplateName = baseRule.getRuleTemplateName();
1150        //use api service to take advantage of caching
1151        List<Rule> rules = KewApiServiceLocator.getRuleService().getRulesByTemplateNameAndDocumentTypeName(
1152                ruleTemplateName, docTypeName);
1153        for (Rule r : rules) {
1154            if (ObjectUtils.equals(rule.isActive(), r.isActive()) &&
1155                        ObjectUtils.equals(docTypeName, r.getDocTypeName()) &&
1156                    ObjectUtils.equals(ruleTemplateName, r.getRuleTemplateName()) &&
1157                    CollectionUtils.collectionsEquivalent(responsibilities, r.getRuleResponsibilities()) &&
1158                    CollectionUtils.collectionsEquivalent(extensions, r.getRuleExtensions())) {
1159
1160                if (ObjectUtils.equals(baseRule.getRuleExpressionDef(), r.getRuleExpressionDef()) ||
1161                    (baseRule.getRuleExpressionDef() != null && r.getRuleExpressionDef() != null) &&
1162                      ObjectUtils.equals(baseRule.getRuleExpressionDef().getType(), r.getRuleExpressionDef().getType()) &&
1163                      ObjectUtils.equals(baseRule.getRuleExpressionDef().getExpression(), r.getRuleExpressionDef().getExpression())) {
1164                // we have a duplicate
1165                    return r.getId();
1166                }
1167            }
1168        }
1169        return null;
1170    }
1171
1172    private void generateRuleNameIfNeeded(RuleBaseValues rule) {
1173        if (StringUtils.isBlank(rule.getName())) {
1174                rule.setName(UUID.randomUUID().toString());
1175        }
1176    }
1177
1178    private void assignResponsibilityIds(RuleBaseValues rule) {
1179        for (RuleResponsibilityBo responsibility : rule.getRuleResponsibilities()) {
1180                if (responsibility.getResponsibilityId() == null) {
1181                        responsibility.setResponsibilityId(KEWServiceLocator.getResponsibilityIdService().getNewResponsibilityId());
1182                }
1183        }
1184    }
1185
1186    public RuleBaseValues saveRule(RuleBaseValues rule, boolean isRetroactiveUpdatePermitted) {
1187        rule.setPreviousRuleId(rule.getId());
1188                rule.setPreviousVersion(null);
1189                rule.setId(null);
1190                makeCurrent(rule, isRetroactiveUpdatePermitted);
1191                return rule;
1192    }
1193
1194    public List<RuleBaseValues> saveRules(List<RuleBaseValues> rulesToSave, boolean isRetroactiveUpdatePermitted) {
1195        List<RuleBaseValues> savedRules = new ArrayList<RuleBaseValues>();
1196        for (RuleBaseValues rule : rulesToSave) {
1197                rule = saveRule(rule, isRetroactiveUpdatePermitted);
1198                savedRules.add(rule);
1199        }
1200        return savedRules;
1201    }
1202
1203    public RuleDelegationBo saveRuleDelegation(RuleDelegationBo ruleDelegation, boolean isRetroactiveUpdatePermitted) {
1204        RuleBaseValues rule = ruleDelegation.getDelegationRule();
1205                rule.setPreviousRuleId(rule.getId());
1206                rule.setPreviousVersion(null);
1207                rule.setId(null);
1208                ruleDelegation.setRuleDelegationId(null);
1209                makeCurrent(ruleDelegation, isRetroactiveUpdatePermitted);
1210                return ruleDelegation;
1211    }
1212
1213    public List<RuleDelegationBo> saveRuleDelegations(List<RuleDelegationBo> ruleDelegationsToSave, boolean isRetroactiveUpdatePermitted) {
1214        List<RuleDelegationBo> savedRuleDelegations = new ArrayList<RuleDelegationBo>();
1215        for (RuleDelegationBo ruleDelegation : ruleDelegationsToSave) {
1216                ruleDelegation = saveRuleDelegation(ruleDelegation, isRetroactiveUpdatePermitted);
1217                savedRuleDelegations.add(ruleDelegation);
1218        }
1219        return savedRuleDelegations;
1220    }
1221
1222    public String findResponsibilityIdForRule(String ruleName, String ruleResponsibilityName, String ruleResponsibilityType) {
1223        return getRuleDAO().findResponsibilityIdForRule(ruleName, ruleResponsibilityName, ruleResponsibilityType);
1224    }
1225
1226    protected String getRuleByTemplateAndDocTypeCacheKey(String ruleTemplateName, String docTypeName) {
1227        return "'templateName=' + " + ruleTemplateName +" '|' + 'documentTypeName=' + " + docTypeName;
1228    }
1229
1230}