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 */ 016package org.kuali.rice.krms.impl.rule; 017 018import org.apache.commons.collections.CollectionUtils; 019import org.apache.commons.lang.StringUtils; 020import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader; 021import org.kuali.rice.core.api.uif.RemotableAttributeError; 022import org.kuali.rice.core.api.util.RiceKeyConstants; 023import org.kuali.rice.kns.maintenance.rules.MaintenanceDocumentRuleBase; 024import org.kuali.rice.krad.bo.GlobalBusinessObject; 025import org.kuali.rice.krad.bo.PersistableBusinessObject; 026import org.kuali.rice.krad.maintenance.MaintenanceDocument; 027import org.kuali.rice.krad.util.KRADConstants; 028import org.kuali.rice.krms.api.KrmsConstants; 029import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; 030import org.kuali.rice.krms.api.repository.rule.RuleDefinition; 031import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition; 032import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService; 033import org.kuali.rice.krms.framework.type.ActionTypeService; 034import org.kuali.rice.krms.framework.type.AgendaTypeService; 035import org.kuali.rice.krms.impl.authorization.AgendaAuthorizationService; 036import org.kuali.rice.krms.impl.repository.*; 037import org.kuali.rice.krms.impl.ui.AgendaEditor; 038import org.kuali.rice.krms.impl.util.KRMSPropertyConstants; 039 040import javax.xml.namespace.QName; 041import java.util.List; 042import java.util.Map; 043 044/** 045 * This class contains the rules for the AgendaEditor. 046 */ 047public class AgendaEditorBusRule extends MaintenanceDocumentRuleBase { 048 049 050 protected boolean primaryKeyCheck(MaintenanceDocument document) { 051 // default to success if no failures 052 boolean success = true; 053 Class<?> dataObjectClass = document.getNewMaintainableObject().getDataObjectClass(); 054 055 // Since the dataObject is a wrapper class we need to return the agendaBo instead. 056 Object oldBo = ((AgendaEditor) document.getOldMaintainableObject().getDataObject()).getAgenda(); 057 Object newDataObject = ((AgendaEditor) document.getNewMaintainableObject().getDataObject()).getAgenda(); 058 059 // We dont do primaryKeyChecks on Global Business Object maintenance documents. This is 060 // because it doesnt really make any sense to do so, given the behavior of Globals. When a 061 // Global Document completes, it will update or create a new record for each BO in the list. 062 // As a result, there's no problem with having existing BO records in the system, they will 063 // simply get updated. 064 if (newDataObject instanceof GlobalBusinessObject) { 065 return success; 066 } 067 068 // fail and complain if the person has changed the primary keys on 069 // an EDIT maintenance document. 070 if (document.isEdit()) { 071 if (!getDataObjectMetaDataService().equalsByPrimaryKeys(oldBo, newDataObject)) { 072 // add a complaint to the errors 073 putDocumentError(KRADConstants.DOCUMENT_ERRORS, 074 RiceKeyConstants.ERROR_DOCUMENT_MAINTENANCE_PRIMARY_KEYS_CHANGED_ON_EDIT, 075 getHumanReadablePrimaryKeyFieldNames(dataObjectClass)); 076 success &= false; 077 } 078 } 079 080 // fail and complain if the person has selected a new object with keys that already exist 081 // in the DB. 082 else if (document.isNew()) { 083 084 // TODO: when/if we have standard support for DO retrieval, do this check for DO's 085 if (newDataObject instanceof PersistableBusinessObject) { 086 087 // get a map of the pk field names and values 088 Map<String, ?> newPkFields = getDataObjectMetaDataService().getPrimaryKeyFieldValues(newDataObject); 089 090 // TODO: Good suggestion from Aaron, dont bother checking the DB, if all of the 091 // objects PK fields dont have values. If any are null or empty, then 092 // we're done. The current way wont fail, but it will make a wasteful 093 // DB call that may not be necessary, and we want to minimize these. 094 095 // attempt to do a lookup, see if this object already exists by these Primary Keys 096 PersistableBusinessObject testBo = getBoService() 097 .findByPrimaryKey(dataObjectClass.asSubclass(PersistableBusinessObject.class), newPkFields); 098 099 // 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