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