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