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