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.repository; 017 018 import java.util.ArrayList; 019 import java.util.Collection; 020 import java.util.Collections; 021 import java.util.HashMap; 022 import java.util.HashSet; 023 import java.util.List; 024 import java.util.Map; 025 import java.util.Map.Entry; 026 import java.util.Set; 027 028 import org.apache.commons.lang.StringUtils; 029 import org.kuali.rice.core.api.CoreApiServiceLocator; 030 import org.kuali.rice.core.api.criteria.CriteriaLookupService; 031 import org.kuali.rice.core.api.criteria.GenericQueryResults; 032 import org.kuali.rice.core.api.criteria.Predicate; 033 import org.kuali.rice.core.api.criteria.QueryByCriteria; 034 import org.kuali.rice.core.api.exception.RiceIllegalArgumentException; 035 import org.kuali.rice.core.api.exception.RiceIllegalStateException; 036 import org.kuali.rice.core.api.mo.ModelObjectUtils; 037 import org.kuali.rice.core.impl.services.CoreImplServiceLocator; 038 import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator; 039 import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator; 040 import org.kuali.rice.krad.service.BusinessObjectService; 041 import org.kuali.rice.krad.service.KRADServiceLocator; 042 import org.kuali.rice.krad.service.SequenceAccessorService; 043 import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition; 044 import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition; 045 import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition; 046 import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames; 047 import org.springframework.util.CollectionUtils; 048 049 import 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 */ 058 public 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 AgendaDefinition existing = getAgendaByAgendaId(agendaId); 145 if (existing == null){ throw new IllegalStateException("the Agenda to delete does not exists: " + agendaId);} 146 businessObjectService.delete(from(existing)); 147 } 148 149 /** 150 * This overridden method retrieves an Agenda from the repository 151 */ 152 @Override 153 public AgendaDefinition getAgendaByAgendaId(String agendaId) { 154 if (StringUtils.isBlank(agendaId)){ 155 throw new RiceIllegalArgumentException("agenda id is null"); 156 } 157 AgendaBo bo = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaId); 158 return to(bo); 159 } 160 161 /** 162 * This overridden method retrieves an agenda from the repository 163 */ 164 @Override 165 public AgendaDefinition getAgendaByNameAndContextId(String name, String contextId) { 166 if (StringUtils.isBlank(name)) { 167 throw new RiceIllegalArgumentException("name is blank"); 168 } 169 if (StringUtils.isBlank(contextId)) { 170 throw new RiceIllegalArgumentException("contextId is blank"); 171 } 172 173 final Map<String, Object> map = new HashMap<String, Object>(); 174 map.put("name", name); 175 map.put("contextId", contextId); 176 177 AgendaBo myAgenda = businessObjectService.findByPrimaryKey(AgendaBo.class, Collections.unmodifiableMap(map)); 178 return to(myAgenda); 179 } 180 181 /** 182 * This overridden method retrieves a set of agendas from the repository 183 */ 184 @Override 185 public List<AgendaDefinition> getAgendasByContextId(String contextId) { 186 if (StringUtils.isBlank(contextId)){ 187 throw new RiceIllegalArgumentException("context ID is null or blank"); 188 } 189 final Map<String, Object> map = new HashMap<String, Object>(); 190 map.put("contextId", contextId); 191 List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map); 192 return convertAgendaBosToImmutables(bos); 193 } 194 195 /** 196 * This overridden method creates a new Agenda in the repository 197 */ 198 @Override 199 public AgendaItemDefinition createAgendaItem(AgendaItemDefinition agendaItem) { 200 if (agendaItem == null){ 201 throw new RiceIllegalArgumentException("agendaItem is null"); 202 } 203 if (agendaItem.getId() != null){ 204 final AgendaDefinition existing = getAgendaByAgendaId(agendaItem.getId()); 205 if (existing != null){ 206 throw new IllegalStateException("the agendaItem to create already exists: " + agendaItem); 207 } 208 } 209 210 AgendaItemBo bo = AgendaItemBo.from(agendaItem); 211 businessObjectService.save(bo); 212 return AgendaItemBo.to(bo); 213 } 214 215 /** 216 * This overridden method updates an existing Agenda in the repository 217 */ 218 @Override 219 public void updateAgendaItem(AgendaItemDefinition agendaItem) { 220 if (agendaItem == null){ 221 throw new RiceIllegalArgumentException("agendaItem is null"); 222 } 223 final String agendaItemIdKey = agendaItem.getId(); 224 final AgendaItemDefinition existing = getAgendaItemById(agendaItemIdKey); 225 if (existing == null) { 226 throw new IllegalStateException("the agenda item does not exist: " + agendaItem); 227 } 228 final AgendaItemDefinition toUpdate; 229 if (existing.getId().equals(agendaItem.getId())) { 230 toUpdate = agendaItem; 231 } else { 232 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem); 233 builder.setId(existing.getId()); 234 toUpdate = builder.build(); 235 } 236 237 businessObjectService.save(AgendaItemBo.from(toUpdate)); 238 } 239 240 /** 241 * This overridden method adds a new AgendaItemDefinition to the repository 242 */ 243 @Override 244 public void addAgendaItem(AgendaItemDefinition agendaItem, String parentId, Boolean position) { 245 if (agendaItem == null){ 246 throw new RiceIllegalArgumentException("agendaItem is null"); 247 } 248 AgendaItemDefinition parent = null; 249 if (parentId != null){ 250 parent = getAgendaItemById(parentId); 251 if (parent == null){ 252 throw new IllegalStateException("parent agendaItem does not exist in repository. parentId = " + parentId); 253 } 254 } 255 // create new AgendaItemDefinition 256 final AgendaItemDefinition toCreate; 257 if (agendaItem.getId() == null) { 258 SequenceAccessorService sas = getSequenceAccessorService(); 259 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem); 260 final String newId =sas.getNextAvailableSequenceNumber( 261 "KRMS_AGENDA_ITM_S", AgendaItemBo.class).toString(); 262 builder.setId(newId); 263 toCreate = builder.build(); 264 } else { 265 toCreate = agendaItem; 266 } 267 createAgendaItem(toCreate); 268 269 // link it to it's parent (for whenTrue/whenFalse, sibling for always 270 if (parentId != null) { 271 final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(parent); 272 if (position == null){ 273 builder.setAlwaysId( toCreate.getId() ); 274 } else if (position.booleanValue()){ 275 builder.setWhenTrueId( toCreate.getId() ); 276 } else if (!position.booleanValue()){ 277 builder.setWhenFalseId( toCreate.getId() ); 278 } 279 final AgendaItemDefinition parentToUpdate = builder.build(); 280 updateAgendaItem( parentToUpdate ); 281 } 282 } 283 284 /** 285 * This overridden method retrieves an AgendaItemDefinition from the repository 286 */ 287 @Override 288 public AgendaItemDefinition getAgendaItemById(String id) { 289 if (StringUtils.isBlank(id)){ 290 throw new RiceIllegalArgumentException("agenda item id is null or blank"); 291 } 292 AgendaItemBo bo = businessObjectService.findBySinglePrimaryKey(AgendaItemBo.class, id); 293 return AgendaItemBo.to(bo); 294 } 295 296 @Override 297 public List<AgendaItemDefinition> getAgendaItemsByAgendaId(String agendaId) { 298 if (StringUtils.isBlank(agendaId)){ 299 throw new RiceIllegalArgumentException("agenda id is null or null"); 300 } 301 List<AgendaItemDefinition> results = null; 302 303 Collection<AgendaItemBo> bos = businessObjectService.findMatching(AgendaItemBo.class, Collections.singletonMap("agendaId", agendaId)); 304 305 if (CollectionUtils.isEmpty(bos)) { 306 results = Collections.emptyList(); 307 } else { 308 results = Collections.unmodifiableList(ModelObjectUtils.transform(bos, toAgendaItemDefinition)); 309 } 310 311 return results; 312 } 313 314 @Override 315 public List<AgendaDefinition> getAgendasByType(String typeId) throws RiceIllegalArgumentException { 316 if (StringUtils.isBlank(typeId)){ 317 throw new RiceIllegalArgumentException("type ID is null or blank"); 318 } 319 final Map<String, Object> map = new HashMap<String, Object>(); 320 map.put("typeId", typeId); 321 List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map); 322 return convertAgendaBosToImmutables(bos); 323 } 324 325 @Override 326 public List<AgendaDefinition> getAgendasByTypeAndContext(String typeId, 327 String contextId) throws RiceIllegalArgumentException { 328 if (StringUtils.isBlank(typeId)){ 329 throw new RiceIllegalArgumentException("type ID is null or blank"); 330 } 331 if (StringUtils.isBlank(contextId)){ 332 throw new RiceIllegalArgumentException("context ID is null or blank"); 333 } 334 final Map<String, Object> map = new HashMap<String, Object>(); 335 map.put("typeId", typeId); 336 map.put("contextId", contextId); 337 Collection<AgendaBo> bos = businessObjectService.findMatching(AgendaBo.class, map); 338 return convertAgendaBosToImmutables(bos); 339 } 340 341 @Override 342 public List<AgendaItemDefinition> getAgendaItemsByType(String typeId) throws RiceIllegalArgumentException { 343 return findAgendaItemsForAgendas(getAgendasByType(typeId)); 344 } 345 346 @Override 347 public List<AgendaItemDefinition> getAgendaItemsByContext(String contextId) throws RiceIllegalArgumentException { 348 return findAgendaItemsForAgendas(getAgendasByContextId(contextId)); 349 } 350 351 @Override 352 public List<AgendaItemDefinition> getAgendaItemsByTypeAndContext(String typeId, 353 String contextId) throws RiceIllegalArgumentException { 354 return findAgendaItemsForAgendas(getAgendasByTypeAndContext(typeId, contextId)); 355 } 356 357 @Override 358 public void deleteAgendaItem(String agendaItemId) throws RiceIllegalArgumentException { 359 if (StringUtils.isBlank(agendaItemId)) { 360 throw new RiceIllegalArgumentException("agendaItemId must not be blank or null"); 361 } 362 363 businessObjectService.deleteMatching(AgendaItemBo.class, Collections.singletonMap("id", agendaItemId)); 364 } 365 366 private List<AgendaItemDefinition> findAgendaItemsForAgendas(List<AgendaDefinition> agendaDefinitions) { 367 List<AgendaItemDefinition> results = null; 368 369 if (!CollectionUtils.isEmpty(agendaDefinitions)) { 370 List<AgendaItemBo> boResults = new ArrayList<AgendaItemBo>(agendaDefinitions.size()); 371 372 List<String> agendaIds = new ArrayList<String>(20); 373 for (AgendaDefinition agendaDefinition : agendaDefinitions) { 374 agendaIds.add(agendaDefinition.getId()); 375 376 if (agendaIds.size() == 20) { 377 // fetch batch 378 379 Predicate predicate = in("agendaId", agendaIds.toArray()); 380 QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate); 381 GenericQueryResults<AgendaItemBo> batch = criteriaLookupService.lookup(AgendaItemBo.class, criteria); 382 383 boResults.addAll(batch.getResults()); 384 385 // reset agendaIds 386 agendaIds.clear(); 387 } 388 } 389 390 if (agendaIds.size() > 0) { 391 Predicate predicate = in("agendaId", agendaIds.toArray()); 392 QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate); 393 GenericQueryResults<AgendaItemBo> batch = criteriaLookupService.lookup(AgendaItemBo.class, criteria); 394 395 boResults.addAll(batch.getResults()); 396 } 397 398 results = Collections.unmodifiableList(ModelObjectUtils.transform(boResults, toAgendaItemDefinition)); 399 } else { 400 results = Collections.emptyList(); 401 } 402 403 return results; 404 } 405 406 public CriteriaLookupService getCriteriaLookupService() { 407 if (criteriaLookupService == null) { 408 criteriaLookupService = KrmsRepositoryServiceLocator.getCriteriaLookupService(); 409 } 410 return criteriaLookupService; 411 } 412 413 public void setCriteriaLookupService(CriteriaLookupService criteriaLookupService) { 414 this.criteriaLookupService = criteriaLookupService; 415 } 416 417 /** 418 * Sets the businessObjectService attribute value. 419 * 420 * @param businessObjectService The businessObjectService to set. 421 */ 422 public void setBusinessObjectService(final BusinessObjectService businessObjectService) { 423 this.businessObjectService = businessObjectService; 424 } 425 426 protected BusinessObjectService getBusinessObjectService() { 427 if ( businessObjectService == null ) { 428 businessObjectService = KRADServiceLocator.getBusinessObjectService(); 429 } 430 return businessObjectService; 431 } 432 433 /** 434 * Sets the sequenceAccessorService attribute value. 435 * 436 * @param sequenceAccessorService The sequenceAccessorService to set. 437 */ 438 public void setSequenceAccessorService(final SequenceAccessorService sequenceAccessorService) { 439 this.sequenceAccessorService = sequenceAccessorService; 440 } 441 442 protected SequenceAccessorService getSequenceAccessorService() { 443 if ( sequenceAccessorService == null ) { 444 sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService(); 445 } 446 return sequenceAccessorService; 447 } 448 449 protected KrmsAttributeDefinitionService getAttributeDefinitionService() { 450 if (attributeDefinitionService == null) { 451 attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService(); 452 } 453 return attributeDefinitionService; 454 } 455 456 public void setAttributeDefinitionService(KrmsAttributeDefinitionService attributeDefinitionService) { 457 this.attributeDefinitionService = attributeDefinitionService; 458 } 459 460 /** 461 * Converts a Set<AgendaBo> to an Unmodifiable Set<Agenda> 462 * 463 * @param agendaBos a mutable Set<AgendaBo> to made completely immutable. 464 * @return An unmodifiable Set<Agenda> 465 */ 466 public List<AgendaDefinition> convertAgendaBosToImmutables(final Collection<AgendaBo> agendaBos) { 467 if (CollectionUtils.isEmpty(agendaBos)) { 468 return Collections.emptyList(); 469 } 470 return Collections.unmodifiableList(ModelObjectUtils.transform(agendaBos, toAgendaDefinition)); 471 } 472 473 /** 474 * Converts a mutable bo to it's immutable counterpart 475 * @param bo the mutable business object 476 * @return the immutable object 477 */ 478 @Override 479 public AgendaDefinition to(AgendaBo bo) { 480 if (bo == null) { return null; } 481 return org.kuali.rice.krms.api.repository.agenda.AgendaDefinition.Builder.create(bo).build(); 482 } 483 484 485 /** 486 * Converts a immutable object to it's mutable bo counterpart 487 * @param im immutable object 488 * @return the mutable bo 489 */ 490 @Override 491 public AgendaBo from(AgendaDefinition im) { 492 if (im == null) { return null; } 493 494 AgendaBo bo = new AgendaBo(); 495 bo.setId(im.getId()); 496 bo.setName( im.getName() ); 497 bo.setTypeId( im.getTypeId() ); 498 bo.setContextId( im.getContextId() ); 499 bo.setFirstItemId( im.getFirstItemId() ); 500 bo.setVersionNumber( im.getVersionNumber() ); 501 Set<AgendaAttributeBo> attributes = buildAgendaAttributeBo(im); 502 503 bo.setAttributeBos(attributes); 504 505 return bo; 506 } 507 508 private Set<AgendaAttributeBo> buildAgendaAttributeBo(AgendaDefinition im) { 509 Set<AgendaAttributeBo> attributes = new HashSet<AgendaAttributeBo>(); 510 511 // build a map from attribute name to definition 512 Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>(); 513 514 List<KrmsAttributeDefinition> attributeDefinitions = 515 getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId()); 516 517 for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) { 518 attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition); 519 } 520 521 // for each entry, build an AgendaAttributeBo and add it to the set 522 for (Entry<String,String> entry : im.getAttributes().entrySet()){ 523 KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey()); 524 525 if (attrDef != null) { 526 AgendaAttributeBo attributeBo = new AgendaAttributeBo(); 527 attributeBo.setAgendaId( im.getId() ); 528 attributeBo.setAttributeDefinitionId(attrDef.getId()); 529 attributeBo.setValue(entry.getValue()); 530 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef)); 531 attributes.add( attributeBo ); 532 } else { 533 throw new RiceIllegalStateException("there is no attribute definition with the name '" + 534 entry.getKey() + "' that is valid for the agenda type with id = '" + im.getTypeId() +"'"); 535 } 536 } 537 return attributes; 538 } 539 540 }