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.krad.service.impl;
017
018import java.util.ArrayList;
019import java.util.List;
020
021import org.apache.commons.lang.StringUtils;
022import org.apache.log4j.Logger;
023import org.kuali.rice.krad.bo.AdHocRoutePerson;
024import org.kuali.rice.krad.bo.AdHocRouteWorkgroup;
025import org.kuali.rice.krad.document.Document;
026import org.kuali.rice.krad.maintenance.MaintenanceDocument;
027import org.kuali.rice.krad.document.TransactionalDocument;
028import org.kuali.rice.krad.exception.InfrastructureException;
029import org.kuali.rice.krad.rules.MaintenanceDocumentRuleBase;
030import org.kuali.rice.krad.rules.TransactionalDocumentRuleBase;
031import org.kuali.rice.krad.rules.rule.BusinessRule;
032import org.kuali.rice.krad.rules.rule.event.AddAdHocRoutePersonEvent;
033import org.kuali.rice.krad.rules.rule.event.AddAdHocRouteWorkgroupEvent;
034import org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent;
035import org.kuali.rice.krad.service.DataDictionaryService;
036import org.kuali.rice.krad.service.DocumentDictionaryService;
037import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
038import org.kuali.rice.krad.service.KualiRuleService;
039import org.kuali.rice.krad.util.GlobalVariables;
040import org.kuali.rice.krad.util.KRADConstants;
041import org.kuali.rice.krad.util.MessageMap;
042
043/**
044 * This class represents a rule evaluator for Kuali. This class is to be used for evaluating business rule checks. The class defines
045 * one method right now - applyRules() which takes in a Document and a DocumentEvent and does the proper business rule checks based
046 * on the context of the event and the document type.
047 */
048public class KualiRuleServiceImpl implements KualiRuleService {
049    private static final Logger LOG = Logger.getLogger(KualiRuleServiceImpl.class);
050
051    private DocumentDictionaryService documentDictionaryService;
052    private DataDictionaryService dataDictionaryService;
053
054    /**
055     * @see org.kuali.rice.krad.service.KualiRuleService#applyRules(org.kuali.rice.krad.rules.rule.event.KualiDocumentEvent)
056     */
057    public boolean applyRules(KualiDocumentEvent event) {
058        if (event == null) {
059            throw new IllegalArgumentException("invalid (null) event");
060        }
061
062        event.validate();
063        if ( LOG.isDebugEnabled() ) {
064                LOG.debug("calling applyRules for event " + event);
065        }
066
067        BusinessRule rule = getBusinessRulesInstance(event.getDocument(), event.getRuleInterfaceClass());
068
069        boolean success = true;
070        if (rule != null) {
071                if ( LOG.isDebugEnabled() ) {   
072                        LOG.debug("processing " + event.getName() + " with rule " + rule.getClass().getName());
073                }
074            increaseErrorPath(event.getErrorPathPrefix());
075
076            // get any child events and apply rules
077            List<KualiDocumentEvent> events = event.generateEvents();
078            for (KualiDocumentEvent generatedEvent : events) {
079                success &= applyRules(generatedEvent);
080            }
081
082            // now call the event rule method
083            success &= event.invokeRuleMethod(rule);
084
085            decreaseErrorPath(event.getErrorPathPrefix());
086
087            // report failures
088            if (!success) {
089                if ( LOG.isDebugEnabled() ) { // NO, this is not a type - only log if in debug mode - this is not an error in production
090                        LOG.debug(event.getName() + " businessRule " + rule.getClass().getName() + " failed");
091                }
092            }
093            else {
094                if ( LOG.isDebugEnabled() ) {
095                        LOG.debug("processed " + event.getName() + " for rule " + rule.getClass().getName());
096                }
097            }
098
099        }
100        return success;
101    }
102
103    /**
104     * Builds a list containing AddAdHocRoutePersonEvents since the validation done for an AdHocRouteRecipient is the same for all
105     * events.
106     * 
107     * @see org.kuali.rice.krad.service.KualiRuleService#generateAdHocRoutePersonEvents(org.kuali.rice.krad.document.Document)
108     */
109    public List<AddAdHocRoutePersonEvent> generateAdHocRoutePersonEvents(Document document) {
110        List<AdHocRoutePerson> adHocRoutePersons = document.getAdHocRoutePersons();
111
112        List<AddAdHocRoutePersonEvent> events = new ArrayList<AddAdHocRoutePersonEvent>();
113
114        for (int i = 0; i < adHocRoutePersons.size(); i++) {
115            events.add(new AddAdHocRoutePersonEvent(
116                    KRADConstants.EXISTING_AD_HOC_ROUTE_PERSON_PROPERTY_NAME + "[" + i + "]", document, adHocRoutePersons.get(i)));
117        }
118
119        return events;
120    }
121
122    /**
123     * Builds a list containing AddAdHocRoutePersonEvents since the validation done for an AdHocRouteRecipient is the same for all
124     * events.
125     * 
126     * @see org.kuali.rice.krad.service.KualiRuleService#generateAdHocRouteWorkgroupEvents(org.kuali.rice.krad.document.Document)
127     */
128    public List<AddAdHocRouteWorkgroupEvent> generateAdHocRouteWorkgroupEvents(Document document) {
129        List<AdHocRouteWorkgroup> adHocRouteWorkgroups = document.getAdHocRouteWorkgroups();
130
131        List<AddAdHocRouteWorkgroupEvent> events = new ArrayList<AddAdHocRouteWorkgroupEvent>();
132
133        for (int i = 0; i < adHocRouteWorkgroups.size(); i++) {
134            events.add(new AddAdHocRouteWorkgroupEvent(
135                    KRADConstants.EXISTING_AD_HOC_ROUTE_WORKGROUP_PROPERTY_NAME + "[" + i + "]", document, adHocRouteWorkgroups.get(i)));
136        }
137
138        return events;
139    }
140    
141
142
143
144
145
146    /**
147     * @param document
148     * @param ruleInterface
149     * @return instance of the businessRulesClass for the given document's type, if that businessRulesClass implements the given
150     *         ruleInterface
151     */
152    public BusinessRule getBusinessRulesInstance(Document document, Class<? extends BusinessRule> ruleInterface) {
153        // get the businessRulesClass
154        Class<? extends BusinessRule> businessRulesClass = null;
155        if (document instanceof TransactionalDocument) {
156            TransactionalDocument transactionalDocument = (TransactionalDocument) document;
157
158            businessRulesClass = getDocumentDictionaryService().getBusinessRulesClass(transactionalDocument);
159            if (businessRulesClass == null) {
160                return new TransactionalDocumentRuleBase(); // default to a generic rule that will enforce Required fields
161            }
162        }
163        else if (document instanceof MaintenanceDocument) {
164            MaintenanceDocument maintenanceDocument = (MaintenanceDocument) document;
165
166            businessRulesClass = getDocumentDictionaryService().getBusinessRulesClass(maintenanceDocument);
167            if (businessRulesClass == null) {
168                return new MaintenanceDocumentRuleBase(); // default to a generic rule that will enforce Required fields
169            }
170        }
171        else {
172            LOG.error("unable to get businessRulesClass for unknown document type '" + document.getClass().getName() + "'");
173        }
174
175        // instantiate and return it if it implements the given ruleInterface
176        BusinessRule rule = null;
177        if (businessRulesClass != null) {
178            try {
179                if (ruleInterface.isAssignableFrom(businessRulesClass)) {
180                    rule = businessRulesClass.newInstance();
181                }
182            }
183            catch (IllegalAccessException e) {
184                throw new InfrastructureException("error processing business rules", e);
185            }
186            catch (InstantiationException e) {
187                throw new InfrastructureException("error processing business rules", e);
188            }
189        }
190
191        return rule;
192    }
193
194    /**
195     * This method increases the registered error path, so that field highlighting can occur on the appropriate object attribute.
196     * 
197     * @param errorPathPrefix
198     */
199    private void increaseErrorPath(String errorPathPrefix) {
200        MessageMap errorMap = GlobalVariables.getMessageMap();
201
202        if (!StringUtils.isBlank(errorPathPrefix)) {
203            errorMap.addToErrorPath(errorPathPrefix);
204        }
205    }
206
207    /**
208     * This method decreases the registered error path, so that field highlighting can occur on the appropriate object attribute.
209     * 
210     * @param errorPathPrefix
211     */
212    private void decreaseErrorPath(String errorPathPrefix) {
213        MessageMap errorMap = GlobalVariables.getMessageMap();
214
215        if (!StringUtils.isBlank(errorPathPrefix)) {
216            errorMap.removeFromErrorPath(errorPathPrefix);
217        }
218    }
219
220    public DocumentDictionaryService getDocumentDictionaryService() {
221        if (documentDictionaryService == null) {
222            this.documentDictionaryService = KRADServiceLocatorWeb.getDocumentDictionaryService();
223        }
224        return documentDictionaryService;
225    }
226
227    public void setDocumentDictionaryService(DocumentDictionaryService documentDictionaryService) {
228        this.documentDictionaryService = documentDictionaryService;
229    }
230
231    public DataDictionaryService getDataDictionaryService() {
232        return dataDictionaryService;
233    }
234
235    public void setDataDictionaryService(DataDictionaryService dataDictionaryService) {
236        this.dataDictionaryService = dataDictionaryService;
237    }
238}