View Javadoc
1   /**
2    * Copyright 2005-2014 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krms.impl.repository;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.core.api.criteria.Predicate;
20  import org.kuali.rice.core.api.criteria.QueryByCriteria;
21  import org.kuali.rice.core.api.criteria.QueryResults;
22  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
23  import org.kuali.rice.core.api.exception.RiceIllegalStateException;
24  import org.kuali.rice.core.api.mo.ModelObjectUtils;
25  import org.kuali.rice.krad.data.DataObjectService;
26  import org.kuali.rice.krad.data.PersistenceOption;
27  import org.kuali.rice.krad.service.KRADServiceLocator;
28  import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
29  import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
30  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
31  import org.springframework.util.CollectionUtils;
32  
33  import java.util.ArrayList;
34  import java.util.Collection;
35  import java.util.Collections;
36  import java.util.HashMap;
37  import java.util.HashSet;
38  import java.util.List;
39  import java.util.Map;
40  import java.util.Map.Entry;
41  import java.util.Set;
42  
43  import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
44  import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.*;
45  
46  /**
47   * Implementation of the interface for accessing KRMS repository Agenda related
48   * business objects. 
49   *
50   * @author Kuali Rice Team (rice.collab@kuali.org)
51   *
52   */
53  public final class AgendaBoServiceImpl implements AgendaBoService {
54  
55      // TODO: deal with active flag
56  
57      private DataObjectService dataObjectService;
58      private KrmsAttributeDefinitionService attributeDefinitionService;
59  
60      // used for converting lists of BOs to model objects
61      private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition =
62              new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() {
63                  public AgendaItemDefinition transform(AgendaItemBo input) {
64                      return AgendaItemBo.to(input);
65                  };
66              };
67  
68      // used for converting lists of BOs to model objects
69      private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition =
70              new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() {
71                  public AgendaDefinition transform(AgendaBo input) {
72                      return AgendaBo.to(input);
73                  };
74              };
75  
76  
77      /**
78       * This overridden method creates a KRMS Agenda in the repository
79       */
80      @Override
81      public AgendaDefinition createAgenda(AgendaDefinition agenda) {
82          if (agenda == null){
83              throw new RiceIllegalArgumentException("agenda is null");
84          }
85          final String nameKey = agenda.getName();
86          final String contextId = agenda.getContextId();
87          final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId);
88          if (existing != null){
89              throw new IllegalStateException("the agenda to create already exists: " + agenda);
90          }
91  
92          AgendaBo agendaBo = from(agenda);
93          agendaBo = dataObjectService.save(agendaBo, PersistenceOption.FLUSH);
94          return to(agendaBo);
95      }
96  
97      /**
98       * This overridden method updates an existing Agenda in the repository
99       */
100     @Override
101     public void updateAgenda(AgendaDefinition agenda) {
102         if (agenda == null){
103             throw new RiceIllegalArgumentException("agenda is null");
104         }
105 
106         // must already exist to be able to update
107         final String agendaIdKey = agenda.getId();
108         final AgendaBo existing = dataObjectService.find(AgendaBo.class, agendaIdKey);
109         if (existing == null) {
110             throw new IllegalStateException("the agenda does not exist: " + agenda);
111         }
112         final AgendaDefinition toUpdate;
113         if (existing.getId().equals(agenda.getId())) {
114             toUpdate = agenda;
115         } else {
116             // if passed in id does not match existing id, correct it
117             final AgendaDefinition.Builder builder = AgendaDefinition.Builder.create(agenda);
118             builder.setId(existing.getId());
119             toUpdate = builder.build();
120         }
121 
122         // copy all updateable fields to bo
123         AgendaBo boToUpdate = from(toUpdate);
124 
125         // move over AgendaBo members that don't get populated from AgendaDefinition
126         boToUpdate.setItems(existing.getItems());
127 
128         // delete any old, existing attributes
129         Map<String,String> fields = new HashMap<String,String>(1);
130         fields.put("agenda.id", toUpdate.getId());
131         deleteMatching(dataObjectService, AgendaAttributeBo.class, fields);
132 
133         // update new agenda and create new attributes
134         dataObjectService.save(boToUpdate, PersistenceOption.FLUSH);
135     }
136 
137     @Override
138     public void deleteAgenda(String agendaId) {
139         if (agendaId == null){ throw new RiceIllegalArgumentException("agendaId is null"); }
140         final AgendaBo bo = dataObjectService.find(AgendaBo.class, agendaId);
141         if (bo == null){ throw new IllegalStateException("the Agenda to delete does not exists: " + agendaId);}
142 
143         List<AgendaItemDefinition> agendaItems = this.getAgendaItemsByAgendaId(bo.getId());
144         for( AgendaItemDefinition agendaItem : agendaItems) {
145             dataObjectService.delete(AgendaItemBo.from(agendaItem));
146         }
147 
148         dataObjectService.delete(bo);
149     }
150 
151     /**
152      * This overridden method retrieves an Agenda from the repository
153      */
154     @Override
155     public AgendaDefinition getAgendaByAgendaId(String agendaId) {
156         if (StringUtils.isBlank(agendaId)){
157             throw new RiceIllegalArgumentException("agenda id is null or blank");
158         }
159         AgendaBo bo = dataObjectService.find(AgendaBo.class, agendaId);
160         return to(bo);
161     }
162 
163     /**
164      * This overridden method retrieves an agenda from the repository
165      */
166     @Override
167     public AgendaDefinition getAgendaByNameAndContextId(String name, String contextId) {
168         if (StringUtils.isBlank(name)) {
169             throw new RiceIllegalArgumentException("name is blank");
170         }
171         if (StringUtils.isBlank(contextId)) {
172             throw new RiceIllegalArgumentException("contextId is blank");
173         }
174 
175         final Map<String, Object> map = new HashMap<String, Object>();
176         map.put("name", name);
177         map.put("contextId", contextId);
178 
179         AgendaBo myAgenda = findSingleMatching(dataObjectService, AgendaBo.class, map);
180         return to(myAgenda);
181     }
182 
183     /**
184      * This overridden method retrieves a set of agendas from the repository
185      */
186     @Override
187     public List<AgendaDefinition> getAgendasByContextId(String contextId) {
188         if (StringUtils.isBlank(contextId)){
189             throw new RiceIllegalArgumentException("context ID is null or blank");
190         }
191         final Map<String, Object> map = new HashMap<String, Object>();
192         map.put("contextId", contextId);
193         List<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
194 
195         return convertAgendaBosToImmutables(bos);
196     }
197 
198     /**
199      * This overridden method creates a new Agenda in the repository
200      */
201     @Override
202     public AgendaItemDefinition createAgendaItem(AgendaItemDefinition agendaItem) {
203         if (agendaItem == null){
204             throw new RiceIllegalArgumentException("agendaItem is null");
205         }
206         if (agendaItem.getId() != null){
207             final AgendaDefinition existing = getAgendaByAgendaId(agendaItem.getId());
208             if (existing != null){
209                 throw new IllegalStateException("the agendaItem to create already exists: " + agendaItem);
210             }
211         }
212 
213         AgendaItemBo bo = AgendaItemBo.from(agendaItem);
214         bo = dataObjectService.save(bo, PersistenceOption.FLUSH);
215         return AgendaItemBo.to(bo);
216     }
217 
218     /**
219      * This overridden method updates an existing Agenda in the repository
220      */
221     @Override
222     public void updateAgendaItem(AgendaItemDefinition agendaItem) {
223         if (agendaItem == null){
224             throw new RiceIllegalArgumentException("agendaItem is null");
225         }
226         final String agendaItemIdKey = agendaItem.getId();
227         final AgendaItemDefinition existing = getAgendaItemById(agendaItemIdKey);
228         if (existing == null) {
229             throw new IllegalStateException("the agenda item does not exist: " + agendaItem);
230         }
231         final AgendaItemDefinition toUpdate;
232         if (existing.getId().equals(agendaItem.getId())) {
233             toUpdate = agendaItem;
234         } else {
235             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
236             builder.setId(existing.getId());
237             toUpdate = builder.build();
238         }
239 
240         AgendaItemBo aiBo = AgendaItemBo.from(toUpdate);
241         updateActionAttributes(aiBo);
242         dataObjectService.save(aiBo, PersistenceOption.FLUSH);
243     }
244 
245     private void updateActionAttributes(AgendaItemBo aiBo) {
246         if(aiBo.getRule()!=null){
247             updateActionAttributes(aiBo.getRule().getActions());
248         }
249         if(aiBo.getWhenTrue()!=null){
250             updateActionAttributes(aiBo.getWhenTrue());
251         }
252         if(aiBo.getWhenFalse()!=null){
253             updateActionAttributes(aiBo.getWhenFalse());
254         }
255         if(aiBo.getAlways()!=null){
256             updateActionAttributes(aiBo.getAlways());
257         }
258     }
259 
260     private void updateActionAttributes(List<ActionBo> actionBos) {
261         for (ActionBo action : actionBos) {
262             for (ActionAttributeBo aa : action.getAttributeBos()) {
263                 Map<String, Object> map = new HashMap<String, Object>();
264                 map.put("actionId", action.getId());
265                 Collection<ActionAttributeBo> aaBos = findMatching(dataObjectService, ActionAttributeBo.class, map);
266 
267                 for (ActionAttributeBo aaBo : aaBos) {
268                     if (StringUtils.equals(aaBo.getAttributeDefinitionId(), aa.getAttributeDefinitionId())) {
269                         aa.setId(aaBo.getId());
270                         aa.setVersionNumber(aaBo.getVersionNumber());
271                     }
272                 }
273             }
274         }
275     }
276 
277     /**
278      * This overridden method adds a new AgendaItemDefinition to the repository
279      */
280     @Override
281     public void addAgendaItem(AgendaItemDefinition agendaItem, String parentId, Boolean position) {
282         if (agendaItem == null){
283             throw new RiceIllegalArgumentException("agendaItem is null");
284         }
285         AgendaItemDefinition parent = null;
286         if (parentId != null){
287             parent = getAgendaItemById(parentId);
288             if (parent == null){
289                 throw new IllegalStateException("parent agendaItem does not exist in repository. parentId = " + parentId);
290             }
291         }
292         // create new AgendaItemDefinition
293         final AgendaItemDefinition toCreate;
294         if (agendaItem.getId() == null) {
295             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
296             builder.setId(AgendaItemBo.agendaItemIdIncrementer.getNewId());
297             toCreate = builder.build();
298         } else {
299             toCreate = agendaItem;
300         }
301         createAgendaItem(toCreate);
302 
303         // link it to it's parent (for whenTrue/whenFalse, sibling for always
304         if (parentId != null) {
305             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(parent);
306             if (position == null){
307                 builder.setAlwaysId( toCreate.getId() );
308             } else if (position.booleanValue()){
309                 builder.setWhenTrueId( toCreate.getId() );
310             } else if (!position.booleanValue()){
311                 builder.setWhenFalseId( toCreate.getId() );
312             }
313             final AgendaItemDefinition parentToUpdate = builder.build();
314             updateAgendaItem( parentToUpdate );
315         }
316     }
317 
318     /**
319      * This overridden method retrieves an AgendaItemDefinition from the repository
320      */
321     @Override
322     public AgendaItemDefinition getAgendaItemById(String id) {
323         if (StringUtils.isBlank(id)){
324             throw new RiceIllegalArgumentException("agenda item id is null or blank");
325         }
326 
327         AgendaItemBo bo = dataObjectService.find(AgendaItemBo.class, id);
328 
329         return AgendaItemBo.to(bo);
330     }
331 
332     @Override
333     public List<AgendaItemDefinition> getAgendaItemsByAgendaId(String agendaId) {
334         if (StringUtils.isBlank(agendaId)){
335             throw new RiceIllegalArgumentException("agenda id is null or null");
336         }
337         List<AgendaItemDefinition> results = null;
338 
339         Collection<AgendaItemBo> bos = findMatching(dataObjectService, AgendaItemBo.class, Collections.singletonMap(
340                 "agendaId", agendaId));
341 
342         if (CollectionUtils.isEmpty(bos)) {
343             results = Collections.emptyList();
344         } else {
345             results = Collections.unmodifiableList(ModelObjectUtils.transform(bos, toAgendaItemDefinition));
346         }
347 
348         return results;
349     }
350 
351     @Override
352     public List<AgendaDefinition> getAgendasByType(String typeId) throws RiceIllegalArgumentException {
353         if (StringUtils.isBlank(typeId)){
354             throw new RiceIllegalArgumentException("type ID is null or blank");
355         }
356 
357         final Map<String, Object> map = new HashMap<String, Object>();
358         map.put("typeId", typeId);
359         List<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
360 
361         return convertAgendaBosToImmutables(bos);
362     }
363 
364     @Override
365     public List<AgendaDefinition> getAgendasByTypeAndContext(String typeId,
366             String contextId) throws RiceIllegalArgumentException {
367         if (StringUtils.isBlank(typeId)){
368             throw new RiceIllegalArgumentException("type ID is null or blank");
369         }
370         if (StringUtils.isBlank(contextId)){
371             throw new RiceIllegalArgumentException("context ID is null or blank");
372         }
373         final Map<String, Object> map = new HashMap<String, Object>();
374         map.put("typeId", typeId);
375         map.put("contextId", contextId);
376         Collection<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
377 
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         deleteMatching(dataObjectService, 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                     QueryResults<AgendaItemBo> batch = getDataObjectService().findMatching(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                 QueryResults<AgendaItemBo> batch = getDataObjectService().findMatching(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     /**
447      * Sets the dataObjectService attribute value.
448      *
449      * @param dataObjectService The dataObjectService to set.
450      */
451     public void setDataObjectService(final DataObjectService dataObjectService) {
452         this.dataObjectService = dataObjectService;
453     }
454 
455     protected DataObjectService getDataObjectService() {
456         if ( dataObjectService == null ) {
457             dataObjectService = KRADServiceLocator.getDataObjectService();
458         }
459         return dataObjectService;
460     }
461 
462     protected KrmsAttributeDefinitionService getAttributeDefinitionService() {
463         if (attributeDefinitionService == null) {
464             attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
465         }
466         return attributeDefinitionService;
467     }
468 
469     public void setAttributeDefinitionService(KrmsAttributeDefinitionService attributeDefinitionService) {
470         this.attributeDefinitionService = attributeDefinitionService;
471     }
472 
473     /**
474      * Converts a Set<AgendaBo> to an Unmodifiable Set<Agenda>
475      *
476      * @param agendaBos a mutable Set<AgendaBo> to made completely immutable.
477      * @return An unmodifiable Set<Agenda>
478      */
479     public List<AgendaDefinition> convertAgendaBosToImmutables(final Collection<AgendaBo> agendaBos) {
480         if (CollectionUtils.isEmpty(agendaBos)) {
481             return Collections.emptyList();
482         }
483         return Collections.unmodifiableList(ModelObjectUtils.transform(agendaBos, toAgendaDefinition));
484     }
485 
486     /**
487      * Converts a mutable bo to it's immutable counterpart
488      * @param bo the mutable business object
489      * @return the immutable object
490      */
491     @Override
492     public AgendaDefinition to(AgendaBo bo) {
493         if (bo == null) { return null; }
494         return org.kuali.rice.krms.api.repository.agenda.AgendaDefinition.Builder.create(bo).build();
495     }
496 
497 
498     /**
499      * Converts a immutable object to it's mutable bo counterpart
500      * @param im immutable object
501      * @return the mutable bo
502      */
503     @Override
504     public AgendaBo from(AgendaDefinition im) {
505         if (im == null) { return null; }
506 
507         AgendaBo bo = new AgendaBo();
508         bo.setId(im.getId());
509         bo.setName( im.getName() );
510         bo.setTypeId( im.getTypeId() );
511         bo.setContextId( im.getContextId() );
512         bo.setFirstItemId( im.getFirstItemId() );
513         bo.setVersionNumber( im.getVersionNumber() );
514         bo.setActive(im.isActive());
515         Set<AgendaAttributeBo> attributes = buildAgendaAttributeBo(im, bo);
516 
517         bo.setAttributeBos(attributes);
518 
519         return bo;
520     }
521 
522     private Set<AgendaAttributeBo> buildAgendaAttributeBo(AgendaDefinition im, AgendaBo agendaBo) {
523         Set<AgendaAttributeBo> attributes = new HashSet<AgendaAttributeBo>();
524 
525         // build a map from attribute name to definition
526         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
527 
528         List<KrmsAttributeDefinition> attributeDefinitions =
529                 getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId());
530 
531         for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
532             attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
533         }
534 
535         // for each entry, build an AgendaAttributeBo and add it to the set
536         for (Entry<String,String> entry  : im.getAttributes().entrySet()){
537             KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey());
538 
539             if (attrDef != null) {
540                 AgendaAttributeBo attributeBo = new AgendaAttributeBo();
541                 attributeBo.setAgenda(agendaBo);
542                 attributeBo.setValue(entry.getValue());
543                 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef));
544                 attributes.add( attributeBo );
545             } else {
546                 throw new RiceIllegalStateException("there is no attribute definition with the name '" +
547                         entry.getKey() + "' that is valid for the agenda type with id = '" + im.getTypeId() +"'");
548             }
549         }
550         return attributes;
551     }
552 
553 }