001 /**
002 * Copyright 2005-2012 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.kuali.rice.krms.impl.rule;
017
018 import org.apache.commons.collections.CollectionUtils;
019 import org.apache.commons.lang.StringUtils;
020 import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
021 import org.kuali.rice.core.api.uif.RemotableAttributeError;
022 import org.kuali.rice.core.api.util.RiceKeyConstants;
023 import org.kuali.rice.krad.bo.GlobalBusinessObject;
024 import org.kuali.rice.krad.bo.PersistableBusinessObject;
025 import org.kuali.rice.krad.maintenance.MaintenanceDocument;
026 import org.kuali.rice.krad.rules.MaintenanceDocumentRuleBase;
027 import org.kuali.rice.krad.util.KRADConstants;
028 import org.kuali.rice.krms.api.KrmsConstants;
029 import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
030 import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
031 import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
032 import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
033 import org.kuali.rice.krms.framework.type.ActionTypeService;
034 import org.kuali.rice.krms.framework.type.AgendaTypeService;
035 import org.kuali.rice.krms.impl.authorization.AgendaAuthorizationService;
036 import org.kuali.rice.krms.impl.repository.ActionBo;
037 import org.kuali.rice.krms.impl.repository.AgendaBo;
038 import org.kuali.rice.krms.impl.repository.AgendaBoService;
039 import org.kuali.rice.krms.impl.repository.AgendaItemBo;
040 import org.kuali.rice.krms.impl.repository.ContextBoService;
041 import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
042 import org.kuali.rice.krms.impl.repository.RuleBo;
043 import org.kuali.rice.krms.impl.repository.RuleBoService;
044 import org.kuali.rice.krms.impl.ui.AgendaEditor;
045 import org.kuali.rice.krms.impl.util.KRMSPropertyConstants;
046
047 import java.util.List;
048 import java.util.Map;
049
050 import javax.xml.namespace.QName;
051
052 /**
053 * This class contains the rules for the AgendaEditor.
054 */
055 public class AgendaEditorBusRule extends MaintenanceDocumentRuleBase {
056
057 @Override
058 protected boolean primaryKeyCheck(MaintenanceDocument document) {
059 // default to success if no failures
060 boolean success = true;
061 Class<?> dataObjectClass = document.getNewMaintainableObject().getDataObjectClass();
062
063 // Since the dataObject is a wrapper class we need to return the agendaBo instead.
064 Object oldBo = ((AgendaEditor) document.getOldMaintainableObject().getDataObject()).getAgenda();
065 Object newDataObject = ((AgendaEditor) document.getNewMaintainableObject().getDataObject()).getAgenda();
066
067 // We dont do primaryKeyChecks on Global Business Object maintenance documents. This is
068 // because it doesnt really make any sense to do so, given the behavior of Globals. When a
069 // Global Document completes, it will update or create a new record for each BO in the list.
070 // As a result, there's no problem with having existing BO records in the system, they will
071 // simply get updated.
072 if (newDataObject instanceof GlobalBusinessObject) {
073 return success;
074 }
075
076 // fail and complain if the person has changed the primary keys on
077 // an EDIT maintenance document.
078 if (document.isEdit()) {
079 if (!getDataObjectMetaDataService().equalsByPrimaryKeys(oldBo, newDataObject)) {
080 // add a complaint to the errors
081 putDocumentError(KRADConstants.DOCUMENT_ERRORS,
082 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT,
083 getHumanReadablePrimaryKeyFieldNames(dataObjectClass));
084 success &= false;
085 }
086 }
087
088 // fail and complain if the person has selected a new object with keys that already exist
089 // in the DB.
090 else if (document.isNew()) {
091
092 // TODO: when/if we have standard support for DO retrieval, do this check for DO's
093 if (newDataObject instanceof PersistableBusinessObject) {
094
095 // get a map of the pk field names and values
096 Map<String, ?> newPkFields = getDataObjectMetaDataService().getPrimaryKeyFieldValues(newDataObject);
097
098 // TODO: Good suggestion from Aaron, dont bother checking the DB, if all of the
099 // 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 = getBoService()
105 .findByPrimaryKey(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 // TODO validate through krms_cntxt_vld_agenda_t
172 if (StringUtils.isBlank(agendaEditor.getNamespace())) {
173 this.putFieldError(KRMSPropertyConstants.Context.NAMESPACE, "error.context.invalidNamespace");
174 return false;
175 }
176 return true;
177 }
178
179 private boolean validAgendaTypeAndAttributes( AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
180 if (validAgendaType(newAgendaEditor.getAgenda().getTypeId(), newAgendaEditor.getAgenda().getContextId())) {
181 return validAgendaAttributes(oldAgendaEditor, newAgendaEditor);
182 } else {
183 return false;
184 }
185 }
186 private boolean validAgendaType(String typeId, String contextId) {
187 boolean isValid = true;
188
189 if (!StringUtils.isBlank(typeId) && !StringUtils.isBlank(contextId)) {
190 if (getKrmsTypeRepositoryService().getAgendaTypeByAgendaTypeIdAndContextId(typeId, contextId) != null) {
191 return true;
192 } else {
193 this.putFieldError(KRMSPropertyConstants.Agenda.TYPE, "error.agenda.invalidType");
194 return false;
195 }
196 }
197
198 return isValid;
199 }
200
201 private boolean validAgendaAttributes(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
202 boolean isValid = true;
203
204 String typeId = newAgendaEditor.getAgenda().getTypeId();
205
206 if (!StringUtils.isEmpty(typeId)) {
207 KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
208
209 if (typeDefinition == null) {
210 throw new IllegalStateException("agenda typeId must match the id of a valid krms type");
211 } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
212 throw new IllegalStateException("agenda type definition must have a non-blank service name");
213 } else {
214 AgendaTypeService agendaTypeService =
215 (AgendaTypeService)KrmsRepositoryServiceLocator.getService(typeDefinition.getServiceName());
216
217 if (agendaTypeService == null) {
218 throw new IllegalStateException("typeDefinition must have a valid serviceName");
219 } else {
220
221 List<RemotableAttributeError> errors;
222 if (oldAgendaEditor == null) {
223 errors = agendaTypeService.validateAttributes(typeId, newAgendaEditor.getCustomAttributesMap());
224 } else {
225 errors = agendaTypeService.validateAttributesAgainstExisting(typeId, newAgendaEditor.getCustomAttributesMap(), oldAgendaEditor.getCustomAttributesMap());
226 }
227
228 if (!CollectionUtils.isEmpty(errors)) {
229 isValid = false;
230 for (RemotableAttributeError error : errors) {
231 for (String errorStr : error.getErrors()) {
232 this.putFieldError(
233 KRMSPropertyConstants.AgendaEditor.CUSTOM_ATTRIBUTES_MAP +
234 "['" + error.getAttributeName() + "']",
235 errorStr
236 );
237 }
238 }
239 }
240 }
241 }
242 }
243 return isValid;
244 }
245
246 /**
247 * Check if an agenda with that name exists already in the context.
248 * @param agendaEditor
249 * @return true if agenda name is unique, false otherwise
250 */
251 public boolean validAgendaName(AgendaEditor agendaEditor) {
252 try {
253 AgendaDefinition agendaFromDataBase = getAgendaBoService().getAgendaByNameAndContextId(
254 agendaEditor.getAgenda().getName(), agendaEditor.getAgenda().getContextId());
255 if ((agendaFromDataBase != null) && !StringUtils.equals(agendaFromDataBase.getId(), agendaEditor.getAgenda().getId())) {
256 this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.duplicateName");
257 return false;
258 }
259 }
260 catch (IllegalArgumentException e) {
261 this.putFieldError(KRMSPropertyConstants.Agenda.NAME, "error.agenda.invalidName");
262 return false;
263 }
264 return true;
265 }
266
267 /**
268 * Check if a agenda item is valid.
269 *
270 * @param document, the Agenda document of the added/edited agenda item
271 * @return true if agenda item is valid, false otherwise
272 */
273 public boolean processAgendaItemBusinessRules(MaintenanceDocument document) {
274 boolean isValid = true;
275
276 AgendaEditor newAgendaEditor = (AgendaEditor) document.getNewMaintainableObject().getDataObject();
277 AgendaEditor oldAgendaEditor = (AgendaEditor) document.getOldMaintainableObject().getDataObject();
278 RuleBo rule = newAgendaEditor.getAgendaItemLine().getRule();
279 isValid &= validateRuleName(rule, newAgendaEditor.getAgenda());
280 isValid &= validRuleType(rule.getTypeId(), newAgendaEditor.getAgenda().getContextId());
281 isValid &= validateRuleAction(oldAgendaEditor, newAgendaEditor);
282
283 return isValid;
284 }
285
286 /**
287 * Check if a rule with that name exists already in the namespace.
288 * @param rule
289 * @parm agenda
290 * @return true if rule name is unique, false otherwise
291 */
292 private boolean validateRuleName(RuleBo rule, AgendaBo agenda) {
293 if (StringUtils.isBlank(rule.getName())) {
294 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
295 return false;
296 }
297 // check current bo for rules (including ones that aren't persisted to the database)
298 for (AgendaItemBo agendaItem : agenda.getItems()) {
299 if (!StringUtils.equals(agendaItem.getRule().getId(), rule.getId()) && StringUtils.equals(agendaItem.getRule().getName(), rule.getName())
300 && StringUtils.equals(agendaItem.getRule().getNamespace(), rule.getNamespace())) {
301 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
302 return false;
303 }
304 }
305
306 // check database for rules used with other agendas - the namespace might not yet be specified on new agendas.
307 if (StringUtils.isNotBlank(rule.getNamespace())) {
308 RuleDefinition ruleFromDatabase = getRuleBoService().getRuleByNameAndNamespace(rule.getName(), rule.getNamespace());
309 try {
310 if ((ruleFromDatabase != null) && !StringUtils.equals(ruleFromDatabase.getId(), rule.getId())) {
311 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.duplicateName");
312 return false;
313 }
314 }
315 catch (IllegalArgumentException e) {
316 this.putFieldError(KRMSPropertyConstants.Rule.NAME, "error.rule.invalidName");
317 return false;
318 }
319 }
320 return true;
321 }
322
323 /**
324 * Check that the rule type is valid when specified.
325 * @param ruleTypeId, the type id
326 * @param contextId, the contextId the action needs to belong to.
327 * @return true if valid, false otherwise.
328 */
329 private boolean validRuleType(String ruleTypeId, String contextId) {
330 if (StringUtils.isBlank(ruleTypeId)) {
331 return true;
332 }
333
334 if (getKrmsTypeRepositoryService().getRuleTypeByRuleTypeIdAndContextId(ruleTypeId, contextId) != null) {
335 return true;
336 } else {
337 this.putFieldError(KRMSPropertyConstants.Rule.TYPE, "error.rule.invalidType");
338 return false;
339 }
340 }
341
342 private boolean validateRuleAction(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
343 boolean isValid = true;
344 ActionBo newActionBo = newAgendaEditor.getAgendaItemLineRuleAction();
345
346 isValid &= validRuleActionType(newActionBo.getTypeId(), newAgendaEditor.getAgenda().getContextId());
347 if (isValid && StringUtils.isNotBlank(newActionBo.getTypeId())) {
348 isValid &= validRuleActionName(newActionBo.getName());
349 isValid &= validRuleActionAttributes(oldAgendaEditor, newAgendaEditor);
350 }
351 return isValid;
352 }
353
354 /**
355 * Check that the rule action type is valid when specified.
356 * @param typeId, the action type id
357 * @parm contextId, the contextId the action needs to belong to.
358 * @return true if valid, false otherwise.
359 */
360 private boolean validRuleActionType(String typeId, String contextId) {
361 if (StringUtils.isBlank(typeId)) {
362 return true;
363 }
364
365 if (getKrmsTypeRepositoryService().getActionTypeByActionTypeIdAndContextId(typeId, contextId) != null) {
366 return true;
367 } else {
368 this.putFieldError(KRMSPropertyConstants.Action.TYPE, "error.action.invalidType");
369 return false;
370 }
371 }
372
373 /**
374 * Check that a action name is specified.
375 */
376 private boolean validRuleActionName(String name) {
377 if (StringUtils.isNotBlank(name)) {
378 return true;
379 } else {
380 this.putFieldError(KRMSPropertyConstants.Action.NAME, "error.action.missingName");
381 return false;
382 }
383 }
384
385 private boolean validRuleActionAttributes(AgendaEditor oldAgendaEditor, AgendaEditor newAgendaEditor) {
386 boolean isValid = true;
387
388 String typeId = newAgendaEditor.getAgendaItemLineRuleAction().getTypeId();
389
390 if (!StringUtils.isBlank(typeId)) {
391 KrmsTypeDefinition typeDefinition = getKrmsTypeRepositoryService().getTypeById(typeId);
392
393 if (typeDefinition == null) {
394 throw new IllegalStateException("rule action typeId must match the id of a valid krms type");
395 } else if (StringUtils.isBlank(typeDefinition.getServiceName())) {
396 throw new IllegalStateException("rule action type definition must have a non-blank service name");
397 } else {
398 ActionTypeService actionTypeService = getActionTypeService(typeDefinition.getServiceName());
399
400 if (actionTypeService == null) {
401 throw new IllegalStateException("typeDefinition must have a valid serviceName");
402 } else {
403
404 List<RemotableAttributeError> errors;
405 if (oldAgendaEditor == null) {
406 errors = actionTypeService.validateAttributes(typeId,
407 newAgendaEditor.getCustomRuleActionAttributesMap());
408 } else {
409 errors = actionTypeService.validateAttributesAgainstExisting(typeId,
410 newAgendaEditor.getCustomRuleActionAttributesMap(), oldAgendaEditor.getCustomRuleActionAttributesMap());
411 }
412
413 if (!CollectionUtils.isEmpty(errors)) {
414 isValid = false;
415 for (RemotableAttributeError error : errors) {
416 for (String errorStr : error.getErrors()) {
417 this.putFieldError(
418 KRMSPropertyConstants.AgendaEditor.CUSTOM_RULE_ACTION_ATTRIBUTES_MAP +
419 "['" + error.getAttributeName() + "']",
420 errorStr
421 );
422 }
423 }
424 }
425 }
426 }
427 }
428 return isValid;
429 }
430
431 public ContextBoService getContextBoService() {
432 return KrmsRepositoryServiceLocator.getContextBoService();
433 }
434
435 public AgendaBoService getAgendaBoService() {
436 return KrmsRepositoryServiceLocator.getAgendaBoService();
437 }
438
439 public RuleBoService getRuleBoService() {
440 return KrmsRepositoryServiceLocator.getRuleBoService();
441 }
442
443 public KrmsTypeRepositoryService getKrmsTypeRepositoryService() {
444 return KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService();
445 }
446
447 public ActionTypeService getActionTypeService(String serviceName) {
448 return (ActionTypeService)GlobalResourceLoader.getService(QName.valueOf(serviceName));
449 }
450 public AgendaAuthorizationService getAgendaAuthorizationService() {
451 return KrmsRepositoryServiceLocator.getAgendaAuthorizationService();
452 }
453
454 }
455