View Javadoc

1   /**
2    * Copyright 2005-2011 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.rule;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.kuali.rice.core.api.uif.RemotableAttributeError;
21  import org.kuali.rice.core.api.util.RiceKeyConstants;
22  import org.kuali.rice.krad.bo.GlobalBusinessObject;
23  import org.kuali.rice.krad.bo.PersistableBusinessObject;
24  import org.kuali.rice.krad.document.MaintenanceDocument;
25  import org.kuali.rice.krad.rules.MaintenanceDocumentRuleBase;
26  import org.kuali.rice.krad.util.KRADConstants;
27  import org.kuali.rice.krms.api.KrmsConstants;
28  import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
29  import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
30  import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
31  import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
32  import org.kuali.rice.krms.framework.type.ActionTypeService;
33  import org.kuali.rice.krms.framework.type.AgendaTypeService;
34  import org.kuali.rice.krms.impl.authorization.AgendaAuthorizationService;
35  import org.kuali.rice.krms.impl.repository.ActionBo;
36  import org.kuali.rice.krms.impl.repository.AgendaBo;
37  import org.kuali.rice.krms.impl.repository.AgendaBoService;
38  import org.kuali.rice.krms.impl.repository.AgendaItemBo;
39  import org.kuali.rice.krms.impl.repository.ContextBoService;
40  import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
41  import org.kuali.rice.krms.impl.repository.RuleBo;
42  import org.kuali.rice.krms.impl.repository.RuleBoService;
43  import org.kuali.rice.krms.impl.ui.AgendaEditor;
44  import org.kuali.rice.krms.impl.util.KRMSPropertyConstants;
45  
46  import java.util.List;
47  import java.util.Map;
48  
49  /**
50   * This class contains the rules for the AgendaEditor.
51   */
52  public class AgendaEditorBusRule extends MaintenanceDocumentRuleBase {
53  
54      @Override
55      protected boolean primaryKeyCheck(MaintenanceDocument document) {
56          // default to success if no failures
57          boolean success = true;
58          Class<?> dataObjectClass = document.getNewMaintainableObject().getDataObjectClass();
59  
60          // Since the dataObject is a wrapper class we need to return the agendaBo instead.
61          Object oldBo = ((AgendaEditor) document.getOldMaintainableObject().getDataObject()).getAgenda();
62          Object newDataObject = ((AgendaEditor) document.getNewMaintainableObject().getDataObject()).getAgenda();
63  
64          // We dont do primaryKeyChecks on Global Business Object maintenance documents. This is
65          // because it doesnt really make any sense to do so, given the behavior of Globals. When a
66          // Global Document completes, it will update or create a new record for each BO in the list.
67          // As a result, there's no problem with having existing BO records in the system, they will
68          // simply get updated.
69          if (newDataObject instanceof GlobalBusinessObject) {
70              return success;
71          }
72  
73          // fail and complain if the person has changed the primary keys on
74          // an EDIT maintenance document.
75          if (document.isEdit()) {
76              if (!getDataObjectMetaDataService().equalsByPrimaryKeys(oldBo, newDataObject)) {
77                  // add a complaint to the errors
78                  putDocumentError(KRADConstants.DOCUMENT_ERRORS,
79                          RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT,
80                          getHumanReadablePrimaryKeyFieldNames(dataObjectClass));
81                  success &= false;
82              }
83          }
84  
85          // fail and complain if the person has selected a new object with keys that already exist
86          // in the DB.
87          else if (document.isNew()) {
88  
89              // TODO: when/if we have standard support for DO retrieval, do this check for DO's
90              if (newDataObject instanceof PersistableBusinessObject) {
91  
92                  // get a map of the pk field names and values
93                  Map<String, ?> newPkFields = getDataObjectMetaDataService().getPrimaryKeyFieldValues(newDataObject);
94  
95                  // TODO: Good suggestion from Aaron, dont bother checking the DB, if all of the
96                  // objects PK fields dont have values. If any are null or empty, then
97                  // we're done. The current way wont fail, but it will make a wasteful
98                  // DB call that may not be necessary, and we want to minimize these.
99  
100                 // attempt to do a lookup, see if this object already exists by these Primary Keys
101                 PersistableBusinessObject testBo = getBoService()
102                         .findByPrimaryKey(dataObjectClass.asSubclass(PersistableBusinessObject.class), newPkFields);
103 
104                 // if the retrieve was successful, then this object already exists, and we need
105                 // to complain
106                 if (testBo != null) {
107                     putDocumentError(KRADConstants.DOCUMENT_ERRORS,
108                             RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_KEYS_ALREADY_EXIST_ON_CREATE_NEW,
109                             getHumanReadablePrimaryKeyFieldNames(dataObjectClass));
110                     success &= false;
111                 }
112             }
113         }
114 
115         return success;
116     }
117 
118 
119 
120     @Override
121     protected boolean processCustomSaveDocumentBusinessRules(MaintenanceDocument document) {
122         boolean isValid = true;
123 
124         AgendaEditor agenda = (AgendaEditor) document.getNewMaintainableObject().getDataObject();
125         AgendaEditor oldAgenda = (AgendaEditor) document.getOldMaintainableObject().getDataObject();
126         isValid &= validContext(agenda);
127         isValid &= validAgendaName(agenda);
128         isValid &= validAgendaAttributes(oldAgenda, agenda);
129 
130         return isValid;
131     }
132 
133     /**
134      * Check if the context exists and if user has authorization to edit agendas under this context.
135      * @param agenda
136      * @return true if the context exist and has authorization, false otherwise
137      */
138     private boolean validContext(AgendaEditor agenda) {
139         boolean isValid = true;
140 
141         try {
142             if (getContextBoService().getContextByContextId(agenda.getAgenda().getContextId()) == null) {
143                 this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.invalidContext");
144                 isValid = false;
145             } else {
146                 if (!getAgendaAuthorizationService().isAuthorized(KrmsConstants.MAINTAIN_KRMS_AGENDA,
147                         agenda.getAgenda().getContextId())) {
148                     this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.unauthorizedContext");
149                     isValid = false;
150                 }
151             }
152         }
153         catch (IllegalArgumentException e) {
154             this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.invalidContext");
155             isValid = false;
156         }
157 
158         return isValid;
159     }
160 
161     private boolean validAgendaAttributes(AgendaEditor oldAgenda, AgendaEditor newAgenda) {
162         boolean isValid = true;
163 
164         String typeId = newAgenda.getAgenda().getTypeId();
165 
166         if (!StringUtils.isEmpty(typeId)) {
167             KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
168 
169             if (typeDefinition == null) {
170                 throw new IllegalStateException("agenda typeId must match the id of a valid krms type");
171             } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
172                 throw new IllegalStateException("agenda type definition must have a non-blank service name");
173             } else {
174                 AgendaTypeService agendaTypeService =
175                         (AgendaTypeService)KrmsRepositoryServiceLocator.getService(typeDefinition.getServiceName());
176 
177                 if (agendaTypeService == null) {
178                     throw new IllegalStateException("typeDefinition must have a valid serviceName");
179                 } else {
180 
181                     List<RemotableAttributeError> errors;
182                     if (oldAgenda == null) {
183                         errors = agendaTypeService.validateAttributes(typeId, newAgenda.getCustomAttributesMap());
184                     } else {
185                         errors = agendaTypeService.validateAttributesAgainstExisting(typeId, newAgenda.getCustomAttributesMap(), oldAgenda.getCustomAttributesMap());
186                     }
187 
188                     if (!CollectionUtils.isEmpty(errors)) {
189                         isValid = false;
190                         for (RemotableAttributeError error : errors) {
191                             for (String errorStr : error.getErrors()) {
192                                 this.putFieldError(
193                                         KRMSPropertyConstants.AgendaEditor.CUSTOM_ATTRIBUTES_MAP +
194                                                 "['" + error.getAttributeName() + "']",
195                                         errorStr
196                                 );
197                             }
198                         }
199                     }
200                 }
201             }
202         }
203         return isValid;
204     }
205 
206     /**
207      * Check if an agenda with that name exists already in the context.
208      * @param agenda
209      * @return true if agenda name is unique, false otherwise
210      */
211     private boolean validAgendaName(AgendaEditor agenda) {
212         try {
213             AgendaDefinition agendaFromDataBase = getAgendaBoService().getAgendaByNameAndContextId(agenda.getAgenda().getName(),
214                     agenda.getAgenda().getContextId());
215             if ((agendaFromDataBase != null) && !StringUtils.equals(agendaFromDataBase.getId(), agenda.getAgenda().getId())) {
216                 this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.duplicateName");
217                 return false;
218             }
219         }
220         catch (IllegalArgumentException e) {
221             this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.invalidName");
222             return false;
223         }
224         return true;
225     }
226 
227     public boolean processAgendaItemBusinessRules(MaintenanceDocument document) {
228         boolean isValid = true;
229 
230         AgendaEditor newAgendaEditor = (AgendaEditor) document.getNewMaintainableObject().getDataObject();
231         AgendaEditor oldAgendaEditor = (AgendaEditor) document.getOldMaintainableObject().getDataObject();
232         RuleBo rule = newAgendaEditor.getAgendaItemLine().getRule();
233         isValid &= validateRuleName(rule, newAgendaEditor.getAgenda());
234         isValid &= validRuleType(rule.getTypeId(), newAgendaEditor.getAgenda().getContextId());
235         isValid &= validateRuleAction(oldAgendaEditor, newAgendaEditor);
236 
237         return isValid;
238     }
239 
240     /**
241      * Check if a rule with that name exists already in the namespace.
242      * @param rule
243      * @parm agenda
244      * @return true if rule name is unique, false otherwise
245      */
246     private boolean validateRuleName(RuleBo rule, AgendaBo agenda) {
247         if (StringUtils.isBlank(rule.getName())) {
248             this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
249             return false;
250         }
251         // check current bo for rules (including ones that aren't persisted to the database)
252         for (AgendaItemBo agendaItem : agenda.getItems()) {
253             if (!StringUtils.equals(agendaItem.getRule().getId(), rule.getId()) && StringUtils.equals(agendaItem.getRule().getName(), rule.getName())
254                     && StringUtils.equals(agendaItem.getRule().getNamespace(), rule.getNamespace())) {
255                 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
256                 return false;
257             }
258         }
259 
260         // check database for rules used with other agendas - the namespace might not yet be specified on new agendas.
261         if (StringUtils.isNotBlank(rule.getNamespace())) {
262             RuleDefinition ruleFromDatabase = getRuleBoService().getRuleByNameAndNamespace(rule.getName(), rule.getNamespace());
263             try {
264                 if ((ruleFromDatabase != null) && !StringUtils.equals(ruleFromDatabase.getId(), rule.getId())) {
265                     this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
266                     return false;
267                 }
268             }
269             catch (IllegalArgumentException e) {
270                 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
271                 return false;
272             }
273         }
274         return true;
275     }
276 
277     /**
278      * Check that the rule type is valid when specified.
279      * @param ruleTypeId, the type id
280      * @param contextId, the contextId the action needs to belong to.
281      * @return true if valid, false otherwise.
282      */
283     private boolean validRuleType(String ruleTypeId, String contextId) {
284         if (StringUtils.isBlank(ruleTypeId)) {
285             return true;
286         }
287 
288         if (getKrmsTypeRepositoryService().getRuleTypeByRuleTypeIdAndContextId(ruleTypeId, contextId) != null) {
289             return true;
290         } else {
291             this.putFieldError(KRMSPropertyConstants.Rule.TYPE, "error.rule.invalidType");
292             return false;
293         }
294     }
295 
296     private boolean validateRuleAction(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
297         boolean isValid = true;
298         ActionBo newActionBo = newAgendaEditor.getAgendaItemLineRuleAction();
299 
300         isValid &= validRuleActionType(newActionBo.getTypeId(), newAgendaEditor.getAgenda().getContextId());
301         if (isValid && StringUtils.isNotBlank(newActionBo.getTypeId())) {
302             isValid &= validRuleActionName(newActionBo.getName());
303             isValid &= validRuleActionAttributes(oldAgendaEditor, newAgendaEditor);
304             isValid &= validRuleActionDescription(newActionBo.getDescription());
305         }
306         return isValid;
307     }
308 
309     /**
310      * Check that the rule action type is valid when specified.
311      * @param typeId, the action type id
312      * @parm contextId, the contextId the action needs to belong to.
313      * @return true if valid, false otherwise.
314      */
315     private boolean validRuleActionType(String typeId, String contextId) {
316         if (StringUtils.isBlank(typeId)) {
317             return true;
318         }
319 
320         if (getKrmsTypeRepositoryService().getActionTypeByActionTypeIdAndContextId(typeId, contextId) != null) {
321             return true;
322         } else {
323             this.putFieldError(KRMSPropertyConstants.Action.TYPE, "error.action.invalidType");
324             return false;
325         }
326     }
327 
328     /**
329      * Check that a action name is specified.
330      */
331     private boolean validRuleActionName(String name) {
332         if (StringUtils.isNotBlank(name)) {
333             return true;
334         } else {
335             this.putFieldError(KRMSPropertyConstants.Action.NAME, "error.action.missingName");
336             return false;
337         }
338     }
339 
340     private boolean validRuleActionAttributes(AgendaEditor oldAgenda, AgendaEditor newAgenda) {
341         boolean isValid = true;
342 
343         String typeId = newAgenda.getAgendaItemLineRuleAction().getTypeId();
344 
345         if (!StringUtils.isBlank(typeId)) {
346             KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
347 
348             if (typeDefinition == null) {
349                 throw new IllegalStateException("rule action typeId must match the id of a valid krms type");
350             } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
351                 throw new IllegalStateException("rule action type definition must have a non-blank service name");
352             } else {
353                 ActionTypeService actionTypeService = getActionTypeService(typeDefinition.getServiceName());
354 
355                 if (actionTypeService == null) {
356                     throw new IllegalStateException("typeDefinition must have a valid serviceName");
357                 } else {
358 
359                     List<RemotableAttributeError> errors;
360                     if (oldAgenda == null) {
361                         errors = actionTypeService.validateAttributes(typeId, newAgenda.getCustomRuleActionAttributesMap());
362                     } else {
363                         errors = actionTypeService.validateAttributesAgainstExisting(typeId, newAgenda.getCustomRuleActionAttributesMap(), oldAgenda.getCustomRuleActionAttributesMap());
364                     }
365 
366                     if (!CollectionUtils.isEmpty(errors)) {
367                         isValid = false;
368                         for (RemotableAttributeError error : errors) {
369                             for (String errorStr : error.getErrors()) {
370                                 this.putFieldError(
371                                         KRMSPropertyConstants.AgendaEditor.CUSTOM_RULE_ACTION_ATTRIBUTES_MAP +
372                                                 "['" + error.getAttributeName() + "']",
373                                         errorStr
374                                 );
375                             }
376                         }
377                     }
378                 }
379             }
380         }
381         return isValid;
382     }
383 
384     /**
385      * Check that a action description is specified.
386      */
387     private boolean validRuleActionDescription(String description) {
388         if (StringUtils.isNotBlank(description)) {
389             return true;
390         } else {
391             this.putFieldError(KRMSPropertyConstants.Action.DESCRIPTION, "error.action.missingDescription");
392             return false;
393         }
394     }
395 
396 
397     public ContextBoService getContextBoService() {
398         return KrmsRepositoryServiceLocator.getContextBoService();
399     }
400 
401     public AgendaBoService getAgendaBoService() {
402         return KrmsRepositoryServiceLocator.getAgendaBoService();
403     }
404 
405     public RuleBoService getRuleBoService() {
406         return KrmsRepositoryServiceLocator.getRuleBoService();
407     }
408 
409     public KrmsTypeRepositoryService getKrmsTypeRepositoryService() {
410         return KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService();
411     }
412 
413     public ActionTypeService getActionTypeService(String serviceName) {
414         return (ActionTypeService)KrmsRepositoryServiceLocator.getService(serviceName);
415     }
416     private AgendaAuthorizationService getAgendaAuthorizationService() {
417         return KrmsRepositoryServiceLocator.getAgendaAuthorizationService();
418     }
419 
420 }
421