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