001/** 002 * Copyright 2005-2016 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.repository; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Map.Entry; 026import java.util.Set; 027 028import org.apache.commons.lang.StringUtils; 029import org.kuali.rice.core.api.CoreApiServiceLocator; 030import org.kuali.rice.core.api.criteria.CriteriaLookupService; 031import org.kuali.rice.core.api.criteria.GenericQueryResults; 032import org.kuali.rice.core.api.criteria.Predicate; 033import org.kuali.rice.core.api.criteria.QueryByCriteria; 034import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 035import org.kuali.rice.core.api.exception.RiceIllegalStateException; 036import org.kuali.rice.core.api.mo.ModelObjectUtils; 037import org.kuali.rice.core.impl.services.CoreImplServiceLocator; 038import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator; 039import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 040import org.kuali.rice.krad.service.BusinessObjectService; 041import org.kuali.rice.krad.service.KRADServiceLocator; 042import org.kuali.rice.krad.service.SequenceAccessorService; 043import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; 044import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition; 045import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 046import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames; 047import org.springframework.util.CollectionUtils; 048 049import static org.kuali.rice.core.api.criteria.PredicateFactory.in; 050 051/** 052 * Implementation of the interface for accessing KRMS repository Agenda related 053 * business objects. 054 * 055 * @author Kuali Rice Team (rice.collab@kuali.org) 056 * 057 */ 058public final class AgendaBoServiceImpl implements AgendaBoService { 059 060 // TODO: deal with active flag 061 062 private BusinessObjectService businessObjectService; 063 private CriteriaLookupService criteriaLookupService; 064 private KrmsAttributeDefinitionService attributeDefinitionService; 065 private SequenceAccessorService sequenceAccessorService; 066 067 // used for converting lists of BOs to model objects 068 private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition = 069 new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() { 070 public AgendaItemDefinition transform(AgendaItemBo input) { 071 return AgendaItemBo.to(input); 072 }; 073 }; 074 075 // used for converting lists of BOs to model objects 076 private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition = 077 new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() { 078 public AgendaDefinition transform(AgendaBo input) { 079 return AgendaBo.to(input); 080 }; 081 }; 082 083 084 /** 085 * This overridden method creates a KRMS Agenda in the repository 086 */ 087 @Override 088 public AgendaDefinition createAgenda(AgendaDefinition agenda) { 089 if (agenda == null){ 090 throw new RiceIllegalArgumentException("agenda is null"); 091 } 092 final String nameKey = agenda.getName(); 093 final String contextId = agenda.getContextId(); 094 final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId); 095 if (existing != null){ 096 throw new IllegalStateException("the agenda to create already exists: " + agenda); 097 } 098 099 AgendaBo agendaBo = from(agenda); 100 businessObjectService.save(agendaBo); 101 return to(agendaBo); 102 } 103 104 /** 105 * This overridden method updates an existing Agenda in the repository 106 */ 107 @Override 108 public void updateAgenda(AgendaDefinition agenda) { 109 if (agenda == null){ 110 throw new RiceIllegalArgumentException("agenda is null"); 111 } 112 113 // must already exist to be able to update 114 final String agendaIdKey = agenda.getId(); 115 final AgendaBo existing = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaIdKey); 116 if (existing == null) { 117 throw new IllegalStateException("the agenda does not exist: " + agenda); 118 } 119 final AgendaDefinition toUpdate; 120 if (existing.getId().equals(agenda.getId())) { 121 toUpdate = agenda; 122 } else { 123 // if passed in id does not match existing id, correct it 124 final AgendaDefinition.Builder builder = AgendaDefinition.Builder.create(agenda); 125 builder.setId(existing.getId()); 126 toUpdate = builder.build(); 127 } 128 129 // copy all updateable fields to bo 130 AgendaBo boToUpdate = from(toUpdate); 131 132 // delete any old, existing attributes 133 Map<String,String> fields = new HashMap<String,String>(1); 134 fields.put(PropertyNames.Agenda.AGENDA_ID, toUpdate.getId()); 135 businessObjectService.deleteMatching(AgendaAttributeBo.class, fields); 136 137 // update new agenda and create new attributes 138 businessObjectService.save(boToUpdate); 139 } 140 141 @Override 142 public void deleteAgenda(String agendaId) { 143 if (agendaId == null){ throw new RiceIllegalArgumentException("agendaId is null"); } 144 final AgendaBo bo = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaId); 145 if (bo == null){ throw new IllegalStateException("the Agenda to delete does not exists: " + agendaId);} 146 147 List<AgendaItemDefinition> agendaItems = this.getAgendaItemsByAgendaId(bo.getId()); 148 for( AgendaItemDefinition agendaItem : agendaItems) { 149 businessObjectService.delete(AgendaItemBo.from(agendaItem)); 150 } 151 152 businessObjectService.delete(bo); 153 } 154 155 /** 156 * This overridden method retrieves an Agenda from the repository 157 */ 158 @Override 159 public AgendaDefinition getAgendaByAgendaId(String agendaId) { 160 if (StringUtils.isBlank(agendaId)){ 161 throw new RiceIllegalArgumentException("agenda id is null or blank"); 162 } 163 AgendaBo bo = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaId); 164 return to(bo); 165 } 166 167 /** 168 * This overridden method retrieves an agenda from the repository 169 */ 170 @Override 171 public AgendaDefinition getAgendaByNameAndContextId(String name, String contextId) { 172 if (StringUtils.isBlank(name)) { 173 throw new RiceIllegalArgumentException("name is blank"); 174 } 175 if (StringUtils.isBlank(contextId)) { 176 throw new RiceIllegalArgumentException("contextId is blank"); 177 } 178 179 final Map<String, Object> map = new HashMap<String, Object>(); 180 map.put("name", name); 181 map.put("contextId", contextId); 182 183 AgendaBo myAgenda = businessObjectService.findByPrimaryKey(AgendaBo.class, Collections.unmodifiableMap(map)); 184 return to(myAgenda); 185 } 186 187 /** 188 * This overridden method retrieves a set of agendas from the repository 189 */ 190 @Override 191 public List<AgendaDefinition> getAgendasByContextId(String contextId) { 192 if (StringUtils.isBlank(contextId)){ 193 throw new RiceIllegalArgumentException("context ID is null or blank"); 194 } 195 final Map<String, Object> map = new HashMap<String, Object>(); 196 map.put("contextId", contextId); 197 List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map); 198 return convertAgendaBosToImmutables(bos); 199 } 200 201 /** 202 * This overridden method creates a new Agenda in the repository 203 */ 204 @Override 205 public AgendaItemDefinition createAgendaItem(AgendaItemDefinition agendaItem) { 206 if (agendaItem == null){ 207 throw new RiceIllegalArgumentException("agendaItem is null"); 208 } 209 if (agendaItem.getId() != null){ 210 final AgendaDefinition existing = getAgendaByAgendaId(agendaItem.getId()); 211 if (existing != null){ 212 throw new IllegalStateException("the agendaItem to create already exists: " + agendaItem); 213 } 214 } 215 216 AgendaItemBo bo = AgendaItemBo.from(agendaItem); 217 businessObjectService.save(bo); 218 return AgendaItemBo.to(bo); 219 } 220 221 /** 222 * This overridden method updates an existing Agenda in the repository 223 */ 224 @Override 225 public void updateAgendaItem(AgendaItemDefinition agendaItem) { 226 if (agendaItem == null){ 227 throw new RiceIllegalArgumentException("agendaItem is null"); 228 } 229 final String agendaItemIdKey = agendaItem.getId(); 230 final AgendaItemDefinition existing = getAgendaItemById(agendaItemIdKey); 231 if (existing == null) { 232 throw new IllegalStateException("the agenda item does not exist: " + agendaItem); 233 } 234 final AgendaItemDefinition toUpdate; 235 if (existing.getId().equals(agendaItem.getId())) { 236 toUpdate = agendaItem; 237 } else { 238 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem); 239 builder.setId(existing.getId()); 240 toUpdate = builder.build(); 241 } 242 243 AgendaItemBo aiBo = AgendaItemBo.from(toUpdate); 244 updateActionAttributes(aiBo); 245 businessObjectService.save(aiBo); 246 } 247 248 private void updateActionAttributes(AgendaItemBo aiBo) { 249 if(aiBo.getRule()!=null){ 250 updateActionAttributes(aiBo.getRule().getActions()); 251 } 252 if(aiBo.getWhenTrue()!=null){ 253 updateActionAttributes(aiBo.getWhenTrue()); 254 } 255 if(aiBo.getWhenFalse()!=null){ 256 updateActionAttributes(aiBo.getWhenFalse()); 257 } 258 if(aiBo.getAlways()!=null){ 259 updateActionAttributes(aiBo.getAlways()); 260 } 261 } 262 263 private void updateActionAttributes(List<ActionBo> actionBos) { 264 for (ActionBo action : actionBos) { 265 for (ActionAttributeBo aa : action.getAttributeBos()) { 266 Map<String, Object> map = new HashMap<String, Object>(); 267 map.put("actionId", action.getId()); 268 Collection<ActionAttributeBo> aaBos = businessObjectService.findMatching(ActionAttributeBo.class, map); 269 270 for (ActionAttributeBo aaBo : aaBos) { 271 if (aaBo.getAttributeDefinitionId().equals(aa.getAttributeDefinitionId())) { 272 aa.setId(aaBo.getId()); 273 aa.setVersionNumber(aaBo.getVersionNumber()); 274 } 275 } 276 } 277 } 278 } 279 280 /** 281 * This overridden method adds a new AgendaItemDefinition to the repository 282 */ 283 @Override 284 public void addAgendaItem(AgendaItemDefinition agendaItem, String parentId, Boolean position) { 285 if (agendaItem == null){ 286 throw new RiceIllegalArgumentException("agendaItem is null"); 287 } 288 AgendaItemDefinition parent = null; 289 if (parentId != null){ 290 parent = getAgendaItemById(parentId); 291 if (parent == null){ 292 throw new IllegalStateException("parent agendaItem does not exist in repository. parentId = " + parentId); 293 } 294 } 295 // create new AgendaItemDefinition 296 final AgendaItemDefinition toCreate; 297 if (agendaItem.getId() == null) { 298 SequenceAccessorService sas = getSequenceAccessorService(); 299 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem); 300 final String newId =sas.getNextAvailableSequenceNumber( 301 "KRMS_AGENDA_ITM_S", AgendaItemBo.class).toString(); 302 builder.setId(newId); 303 toCreate = builder.build(); 304 } else { 305 toCreate = agendaItem; 306 } 307 createAgendaItem(toCreate); 308 309 // link it to it's parent (for whenTrue/whenFalse, sibling for always 310 if (parentId != null) { 311 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(parent); 312 if (position == null){ 313 builder.setAlwaysId( toCreate.getId() ); 314 } else if (position.booleanValue()){ 315 builder.setWhenTrueId( toCreate.getId() ); 316 } else if (!position.booleanValue()){ 317 builder.setWhenFalseId( toCreate.getId() ); 318 } 319 final AgendaItemDefinition parentToUpdate = builder.build(); 320 updateAgendaItem( parentToUpdate ); 321 } 322 } 323 324 /** 325 * This overridden method retrieves an AgendaItemDefinition from the repository 326 */ 327 @Override 328 public AgendaItemDefinition getAgendaItemById(String id) { 329 if (StringUtils.isBlank(id)){ 330 throw new RiceIllegalArgumentException("agenda item id is null or blank"); 331 } 332 AgendaItemBo bo = businessObjectService.findBySinglePrimaryKey(AgendaItemBo.class, id); 333 return AgendaItemBo.to(bo); 334 } 335 336 @Override 337 public List<AgendaItemDefinition> getAgendaItemsByAgendaId(String agendaId) { 338 if (StringUtils.isBlank(agendaId)){ 339 throw new RiceIllegalArgumentException("agenda id is null or null"); 340 } 341 List<AgendaItemDefinition> results = null; 342 343 Collection<AgendaItemBo> bos = businessObjectService.findMatching(AgendaItemBo.class, Collections.singletonMap("agendaId", agendaId)); 344 345 if (CollectionUtils.isEmpty(bos)) { 346 results = Collections.emptyList(); 347 } else { 348 results = Collections.unmodifiableList(ModelObjectUtils.transform(bos, toAgendaItemDefinition)); 349 } 350 351 return results; 352 } 353 354 @Override 355 public List<AgendaDefinition> getAgendasByType(String typeId) throws RiceIllegalArgumentException { 356 if (StringUtils.isBlank(typeId)){ 357 throw new RiceIllegalArgumentException("type ID is null or blank"); 358 } 359 final Map<String, Object> map = new HashMap<String, Object>(); 360 map.put("typeId", typeId); 361 List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map); 362 return convertAgendaBosToImmutables(bos); 363 } 364 365 @Override 366 public List<AgendaDefinition> getAgendasByTypeAndContext(String typeId, 367 String contextId) throws RiceIllegalArgumentException { 368 if (StringUtils.isBlank(typeId)){ 369 throw new RiceIllegalArgumentException("type ID is null or blank"); 370 } 371 if (StringUtils.isBlank(contextId)){ 372 throw new RiceIllegalArgumentException("context ID is null or blank"); 373 } 374 final Map<String, Object> map = new HashMap<String, Object>(); 375 map.put("typeId", typeId); 376 map.put("contextId", contextId); 377 Collection<AgendaBo> bos = businessObjectService.findMatching(AgendaBo.class, map); 378 return convertAgendaBosToImmutables(bos); 379 } 380 381 @Override 382 public List<AgendaItemDefinition> getAgendaItemsByType(String typeId) throws RiceIllegalArgumentException { 383 return findAgendaItemsForAgendas(getAgendasByType(typeId)); 384 } 385 386 @Override 387 public List<AgendaItemDefinition> getAgendaItemsByContext(String contextId) throws RiceIllegalArgumentException { 388 return findAgendaItemsForAgendas(getAgendasByContextId(contextId)); 389 } 390 391 @Override 392 public List<AgendaItemDefinition> getAgendaItemsByTypeAndContext(String typeId, 393 String contextId) throws RiceIllegalArgumentException { 394 return findAgendaItemsForAgendas(getAgendasByTypeAndContext(typeId, contextId)); 395 } 396 397 @Override 398 public void deleteAgendaItem(String agendaItemId) throws RiceIllegalArgumentException { 399 if (StringUtils.isBlank(agendaItemId)) { 400 throw new RiceIllegalArgumentException("agendaItemId must not be blank or null"); 401 } 402 403 businessObjectService.deleteMatching(AgendaItemBo.class, Collections.singletonMap("id", agendaItemId)); 404 } 405 406 private List<AgendaItemDefinition> findAgendaItemsForAgendas(List<AgendaDefinition> agendaDefinitions) { 407 List<AgendaItemDefinition> results = null; 408 409 if (!CollectionUtils.isEmpty(agendaDefinitions)) { 410 List<AgendaItemBo> boResults = new ArrayList<AgendaItemBo>(agendaDefinitions.size()); 411 412 List<String> agendaIds = new ArrayList<String>(20); 413 for (AgendaDefinition agendaDefinition : agendaDefinitions) { 414 agendaIds.add(agendaDefinition.getId()); 415 416 if (agendaIds.size() == 20) { 417 // fetch batch 418 419 Predicate predicate = in("agendaId", agendaIds.toArray()); 420 QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate); 421 GenericQueryResults<AgendaItemBo> batch = this.getCriteriaLookupService().lookup(AgendaItemBo.class, criteria); 422 423 boResults.addAll(batch.getResults()); 424 425 // reset agendaIds 426 agendaIds.clear(); 427 } 428 } 429 430 if (agendaIds.size() > 0) { 431 Predicate predicate = in("agendaId", agendaIds.toArray()); 432 QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate); 433 GenericQueryResults<AgendaItemBo> batch = this.getCriteriaLookupService().lookup(AgendaItemBo.class, criteria); 434 435 boResults.addAll(batch.getResults()); 436 } 437 438 results = Collections.unmodifiableList(ModelObjectUtils.transform(boResults, toAgendaItemDefinition)); 439 } else { 440 results = Collections.emptyList(); 441 } 442 443 return results; 444 } 445 446 public CriteriaLookupService getCriteriaLookupService() { 447 if (criteriaLookupService == null) { 448 criteriaLookupService = KrmsRepositoryServiceLocator.getCriteriaLookupService(); 449 } 450 return criteriaLookupService; 451 } 452 453 public void setCriteriaLookupService(CriteriaLookupService criteriaLookupService) { 454 this.criteriaLookupService = criteriaLookupService; 455 } 456 457 /** 458 * Sets the businessObjectService attribute value. 459 * 460 * @param businessObjectService The businessObjectService to set. 461 */ 462 public void setBusinessObjectService(final BusinessObjectService businessObjectService) { 463 this.businessObjectService = businessObjectService; 464 } 465 466 protected BusinessObjectService getBusinessObjectService() { 467 if ( businessObjectService == null ) { 468 businessObjectService = KRADServiceLocator.getBusinessObjectService(); 469 } 470 return businessObjectService; 471 } 472 473 /** 474 * Sets the sequenceAccessorService attribute value. 475 * 476 * @param sequenceAccessorService The sequenceAccessorService to set. 477 */ 478 public void setSequenceAccessorService(final SequenceAccessorService sequenceAccessorService) { 479 this.sequenceAccessorService = sequenceAccessorService; 480 } 481 482 protected SequenceAccessorService getSequenceAccessorService() { 483 if ( sequenceAccessorService == null ) { 484 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService(); 485 } 486 return sequenceAccessorService; 487 } 488 489 protected KrmsAttributeDefinitionService getAttributeDefinitionService() { 490 if (attributeDefinitionService == null) { 491 attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService(); 492 } 493 return attributeDefinitionService; 494 } 495 496 public void setAttributeDefinitionService(KrmsAttributeDefinitionService attributeDefinitionService) { 497 this.attributeDefinitionService = attributeDefinitionService; 498 } 499 500 /** 501 * Converts a Set<AgendaBo> to an Unmodifiable Set<Agenda> 502 * 503 * @param agendaBos a mutable Set<AgendaBo> to made completely immutable. 504 * @return An unmodifiable Set<Agenda> 505 */ 506 public List<AgendaDefinition> convertAgendaBosToImmutables(final Collection<AgendaBo> agendaBos) { 507 if (CollectionUtils.isEmpty(agendaBos)) { 508 return Collections.emptyList(); 509 } 510 return Collections.unmodifiableList(ModelObjectUtils.transform(agendaBos, toAgendaDefinition)); 511 } 512 513 /** 514 * Converts a mutable bo to it's immutable counterpart 515 * @param bo the mutable business object 516 * @return the immutable object 517 */ 518 @Override 519 public AgendaDefinition to(AgendaBo bo) { 520 if (bo == null) { return null; } 521 return org.kuali.rice.krms.api.repository.agenda.AgendaDefinition.Builder.create(bo).build(); 522 } 523 524 525 /** 526 * Converts a immutable object to it's mutable bo counterpart 527 * @param im immutable object 528 * @return the mutable bo 529 */ 530 @Override 531 public AgendaBo from(AgendaDefinition im) { 532 if (im == null) { return null; } 533 534 AgendaBo bo = new AgendaBo(); 535 bo.setId(im.getId()); 536 bo.setName( im.getName() ); 537 bo.setTypeId( im.getTypeId() ); 538 bo.setContextId( im.getContextId() ); 539 bo.setFirstItemId( im.getFirstItemId() ); 540 bo.setVersionNumber( im.getVersionNumber() ); 541 bo.setActive(im.isActive()); 542 Set<AgendaAttributeBo> attributes = buildAgendaAttributeBo(im); 543 544 bo.setAttributeBos(attributes); 545 546 return bo; 547 } 548 549 private Set<AgendaAttributeBo> buildAgendaAttributeBo(AgendaDefinition im) { 550 Set<AgendaAttributeBo> attributes = new HashSet<AgendaAttributeBo>(); 551 552 // build a map from attribute name to definition 553 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 554 555 List<KrmsAttributeDefinition> attributeDefinitions = 556 getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId()); 557 558 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 559 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 560 } 561 562 // for each entry, build an AgendaAttributeBo and add it to the set 563 for (Entry<String,String> entry : im.getAttributes().entrySet()){ 564 KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); 565 566 if (attrDef != null) { 567 AgendaAttributeBo attributeBo = new AgendaAttributeBo(); 568 attributeBo.setAgendaId( im.getId() ); 569 attributeBo.setAttributeDefinitionId(attrDef.getId()); 570 attributeBo.setValue(entry.getValue()); 571 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef)); 572 attributes.add( attributeBo ); 573 } else { 574 throw new RiceIllegalStateException("there is no attribute definition with the name '" + 575 entry.getKey() + "' that is valid for the agenda type with id = '" + im.getTypeId() +"'"); 576 } 577 } 578 return attributes; 579 } 580 581}