View Javadoc

1   /**
2    * Copyright 2005-2012 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.maintenance.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 agendaEditor = (AgendaEditor) document.getNewMaintainableObject().getDataObject();
125         AgendaEditor oldAgendaEditor = (AgendaEditor) document.getOldMaintainableObject().getDataObject();
126         isValid &= validContext(agendaEditor);
127         isValid &= validAgendaName(agendaEditor);
128         isValid &= validContextAgendaNamespace(agendaEditor);
129         isValid &= validAgendaTypeAndAttributes(oldAgendaEditor, agendaEditor);
130 
131         return isValid;
132     }
133 
134     /**
135      * Check if the context exists and if user has authorization to edit agendas under this context.
136      * @param agendaEditor
137      * @return true if the context exist and has authorization, false otherwise
138      */
139     public boolean validContext(AgendaEditor agendaEditor) {
140         boolean isValid = true;
141 
142         try {
143             if (getContextBoService().getContextByContextId(agendaEditor.getAgenda().getContextId()) == null) {
144                 this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.invalidContext");
145                 isValid = false;
146             } else {
147                 if (!getAgendaAuthorizationService().isAuthorized(KrmsConstants.MAINTAIN_KRMS_AGENDA,
148                         agendaEditor.getAgenda().getContextId())) {
149                     this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.unauthorizedContext");
150                     isValid = false;
151                 }
152             }
153         }
154         catch (IllegalArgumentException e) {
155             this.putFieldError(KRMSPropertyConstants.Agenda.CONTEXT, "error.agenda.invalidContext");
156             isValid = false;
157         }
158 
159         return isValid;
160     }
161 
162     /**
163      * Check if for namespace.
164      * @param agendaEditor
165      * @return
166      */
167     public boolean validContextAgendaNamespace(AgendaEditor agendaEditor) {
168         // TODO validate through krms_cntxt_vld_agenda_t
169         if (StringUtils.isBlank(agendaEditor.getNamespace())) {
170             this.putFieldError(KRMSPropertyConstants.Context.NAMESPACE, "error.context.invalidNamespace");
171             return false;
172         }
173         return true;
174     }
175 
176     private boolean validAgendaTypeAndAttributes( AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
177         if (validAgendaType(newAgendaEditor.getAgenda().getTypeId(), newAgendaEditor.getAgenda().getContextId())) {
178             return validAgendaAttributes(oldAgendaEditor, newAgendaEditor);
179         } else {
180             return false;
181         }
182     }
183     private boolean validAgendaType(String typeId, String contextId) {
184         boolean isValid = true;
185 
186         if (!StringUtils.isBlank(typeId) && !StringUtils.isBlank(contextId)) {
187             if (getKrmsTypeRepositoryService().getAgendaTypeByAgendaTypeIdAndContextId(typeId, contextId) != null) {
188                 return true;
189             } else {
190                 this.putFieldError(KRMSPropertyConstants.Agenda.TYPE, "error.agenda.invalidType");
191                 return false;
192             }
193         }
194 
195         return isValid;
196     }
197 
198     private boolean validAgendaAttributes(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
199         boolean isValid = true;
200 
201         String typeId = newAgendaEditor.getAgenda().getTypeId();
202 
203         if (!StringUtils.isEmpty(typeId)) {
204             KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
205 
206             if (typeDefinition == null) {
207                 throw new IllegalStateException("agenda typeId must match the id of a valid krms type");
208             } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
209                 throw new IllegalStateException("agenda type definition must have a non-blank service name");
210             } else {
211                 AgendaTypeService agendaTypeService =
212                         (AgendaTypeService)KrmsRepositoryServiceLocator.getService(typeDefinition.getServiceName());
213 
214                 if (agendaTypeService == null) {
215                     throw new IllegalStateException("typeDefinition must have a valid serviceName");
216                 } else {
217 
218                     List<RemotableAttributeError> errors;
219                     if (oldAgendaEditor == null) {
220                         errors = agendaTypeService.validateAttributes(typeId, newAgendaEditor.getCustomAttributesMap());
221                     } else {
222                         errors = agendaTypeService.validateAttributesAgainstExisting(typeId, newAgendaEditor.getCustomAttributesMap(), oldAgendaEditor.getCustomAttributesMap());
223                     }
224 
225                     if (!CollectionUtils.isEmpty(errors)) {
226                         isValid = false;
227                         for (RemotableAttributeError error : errors) {
228                             for (String errorStr : error.getErrors()) {
229                                 this.putFieldError(
230                                         KRMSPropertyConstants.AgendaEditor.CUSTOM_ATTRIBUTES_MAP +
231                                                 "['" + error.getAttributeName() + "']",
232                                         errorStr
233                                 );
234                             }
235                         }
236                     }
237                 }
238             }
239         }
240         return isValid;
241     }
242 
243     /**
244      * Check if an agenda with that name exists already in the context.
245      * @param agendaEditor
246      * @return true if agenda name is unique, false otherwise
247      */
248     public boolean validAgendaName(AgendaEditor agendaEditor) {
249         try {
250             AgendaDefinition agendaFromDataBase = getAgendaBoService().getAgendaByNameAndContextId(
251                     agendaEditor.getAgenda().getName(), agendaEditor.getAgenda().getContextId());
252             if ((agendaFromDataBase != null) && !StringUtils.equals(agendaFromDataBase.getId(), agendaEditor.getAgenda().getId())) {
253                 this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.duplicateName");
254                 return false;
255             }
256         }
257         catch (IllegalArgumentException e) {
258             this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.invalidName");
259             return false;
260         }
261         return true;
262     }
263 
264     /**
265      * Check if a agenda item is valid.
266      *
267      * @param document, the Agenda document of the added/edited agenda item
268      * @return true if agenda item is valid, false otherwise
269      */
270     public boolean processAgendaItemBusinessRules(MaintenanceDocument document) {
271         boolean isValid = true;
272 
273         AgendaEditor newAgendaEditor = (AgendaEditor) document.getNewMaintainableObject().getDataObject();
274         AgendaEditor oldAgendaEditor = (AgendaEditor) document.getOldMaintainableObject().getDataObject();
275         RuleBo rule = newAgendaEditor.getAgendaItemLine().getRule();
276         isValid &= validateRuleName(rule, newAgendaEditor.getAgenda());
277         isValid &= validRuleType(rule.getTypeId(), newAgendaEditor.getAgenda().getContextId());
278         isValid &= validateRuleAction(oldAgendaEditor, newAgendaEditor);
279 
280         return isValid;
281     }
282 
283     /**
284      * Check if a rule with that name exists already in the namespace.
285      * @param rule
286      * @parm agenda
287      * @return true if rule name is unique, false otherwise
288      */
289     private boolean validateRuleName(RuleBo rule, AgendaBo agenda) {
290         if (StringUtils.isBlank(rule.getName())) {
291             this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
292             return false;
293         }
294         // check current bo for rules (including ones that aren't persisted to the database)
295         for (AgendaItemBo agendaItem : agenda.getItems()) {
296             if (!StringUtils.equals(agendaItem.getRule().getId(), rule.getId()) && StringUtils.equals(agendaItem.getRule().getName(), rule.getName())
297                     && StringUtils.equals(agendaItem.getRule().getNamespace(), rule.getNamespace())) {
298                 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
299                 return false;
300             }
301         }
302 
303         // check database for rules used with other agendas - the namespace might not yet be specified on new agendas.
304         if (StringUtils.isNotBlank(rule.getNamespace())) {
305             RuleDefinition ruleFromDatabase = getRuleBoService().getRuleByNameAndNamespace(rule.getName(), rule.getNamespace());
306             try {
307                 if ((ruleFromDatabase != null) && !StringUtils.equals(ruleFromDatabase.getId(), rule.getId())) {
308                     this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
309                     return false;
310                 }
311             }
312             catch (IllegalArgumentException e) {
313                 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
314                 return false;
315             }
316         }
317         return true;
318     }
319 
320     /**
321      * Check that the rule type is valid when specified.
322      * @param ruleTypeId, the type id
323      * @param contextId, the contextId the action needs to belong to.
324      * @return true if valid, false otherwise.
325      */
326     private boolean validRuleType(String ruleTypeId, String contextId) {
327         if (StringUtils.isBlank(ruleTypeId)) {
328             return true;
329         }
330 
331         if (getKrmsTypeRepositoryService().getRuleTypeByRuleTypeIdAndContextId(ruleTypeId, contextId) != null) {
332             return true;
333         } else {
334             this.putFieldError(KRMSPropertyConstants.Rule.TYPE, "error.rule.invalidType");
335             return false;
336         }
337     }
338 
339     private boolean validateRuleAction(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
340         boolean isValid = true;
341         ActionBo newActionBo = newAgendaEditor.getAgendaItemLineRuleAction();
342 
343         isValid &= validRuleActionType(newActionBo.getTypeId(), newAgendaEditor.getAgenda().getContextId());
344         if (isValid && StringUtils.isNotBlank(newActionBo.getTypeId())) {
345             isValid &= validRuleActionName(newActionBo.getName());
346             isValid &= validRuleActionAttributes(oldAgendaEditor, newAgendaEditor);
347         }
348         return isValid;
349     }
350 
351     /**
352      * Check that the rule action type is valid when specified.
353      * @param typeId, the action type id
354      * @parm contextId, the contextId the action needs to belong to.
355      * @return true if valid, false otherwise.
356      */
357     private boolean validRuleActionType(String typeId, String contextId) {
358         if (StringUtils.isBlank(typeId)) {
359             return true;
360         }
361 
362         if (getKrmsTypeRepositoryService().getActionTypeByActionTypeIdAndContextId(typeId, contextId) != null) {
363             return true;
364         } else {
365             this.putFieldError(KRMSPropertyConstants.Action.TYPE, "error.action.invalidType");
366             return false;
367         }
368     }
369 
370     /**
371      * Check that a action name is specified.
372      */
373     private boolean validRuleActionName(String name) {
374         if (StringUtils.isNotBlank(name)) {
375             return true;
376         } else {
377             this.putFieldError(KRMSPropertyConstants.Action.NAME, "error.action.missingName");
378             return false;
379         }
380     }
381 
382     private boolean validRuleActionAttributes(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
383         boolean isValid = true;
384 
385         String typeId = newAgendaEditor.getAgendaItemLineRuleAction().getTypeId();
386 
387         if (!StringUtils.isBlank(typeId)) {
388             KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
389 
390             if (typeDefinition == null) {
391                 throw new IllegalStateException("rule action typeId must match the id of a valid krms type");
392             } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
393                 throw new IllegalStateException("rule action type definition must have a non-blank service name");
394             } else {
395                 ActionTypeService actionTypeService = getActionTypeService(typeDefinition.getServiceName());
396 
397                 if (actionTypeService == null) {
398                     throw new IllegalStateException("typeDefinition must have a valid serviceName");
399                 } else {
400 
401                     List<RemotableAttributeError> errors;
402                     if (oldAgendaEditor == null) {
403                         errors = actionTypeService.validateAttributes(typeId,
404                                 newAgendaEditor.getCustomRuleActionAttributesMap());
405                     } else {
406                         errors = actionTypeService.validateAttributesAgainstExisting(typeId,
407                                 newAgendaEditor.getCustomRuleActionAttributesMap(), oldAgendaEditor.getCustomRuleActionAttributesMap());
408                     }
409 
410                     if (!CollectionUtils.isEmpty(errors)) {
411                         isValid = false;
412                         for (RemotableAttributeError error : errors) {
413                             for (String errorStr : error.getErrors()) {
414                                 this.putFieldError(
415                                         KRMSPropertyConstants.AgendaEditor.CUSTOM_RULE_ACTION_ATTRIBUTES_MAP +
416                                                 "['" + error.getAttributeName() + "']",
417                                         errorStr
418                                 );
419                             }
420                         }
421                     }
422                 }
423             }
424         }
425         return isValid;
426     }
427 
428     public ContextBoService getContextBoService() {
429         return KrmsRepositoryServiceLocator.getContextBoService();
430     }
431 
432     public AgendaBoService getAgendaBoService() {
433         return KrmsRepositoryServiceLocator.getAgendaBoService();
434     }
435 
436     public RuleBoService getRuleBoService() {
437         return KrmsRepositoryServiceLocator.getRuleBoService();
438     }
439 
440     public KrmsTypeRepositoryService getKrmsTypeRepositoryService() {
441         return KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService();
442     }
443 
444     public ActionTypeService getActionTypeService(String serviceName) {
445         return (ActionTypeService)KrmsRepositoryServiceLocator.getService(serviceName);
446     }
447     public AgendaAuthorizationService getAgendaAuthorizationService() {
448         return KrmsRepositoryServiceLocator.getAgendaAuthorizationService();
449     }
450 
451 }
452