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