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