View Javadoc

1   /**
2    * Copyright 2005-2013 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 java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Collections;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Map.Entry;
26  import java.util.Set;
27  
28  import org.apache.commons.lang.StringUtils;
29  import org.kuali.rice.core.api.CoreApiServiceLocator;
30  import org.kuali.rice.core.api.criteria.CriteriaLookupService;
31  import org.kuali.rice.core.api.criteria.GenericQueryResults;
32  import org.kuali.rice.core.api.criteria.Predicate;
33  import org.kuali.rice.core.api.criteria.QueryByCriteria;
34  import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
35  import org.kuali.rice.core.api.exception.RiceIllegalStateException;
36  import org.kuali.rice.core.api.mo.ModelObjectUtils;
37  import org.kuali.rice.core.impl.services.CoreImplServiceLocator;
38  import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
39  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
40  import org.kuali.rice.krad.service.BusinessObjectService;
41  import org.kuali.rice.krad.service.KRADServiceLocator;
42  import org.kuali.rice.krad.service.SequenceAccessorService;
43  import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
44  import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
45  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
46  import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
47  import org.springframework.util.CollectionUtils;
48  
49  import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
50  
51  /**
52   * Implementation of the interface for accessing KRMS repository Agenda related
53   * business objects. 
54   *
55   * @author Kuali Rice Team (rice.collab@kuali.org)
56   *
57   */
58  public final class KSAgendaBoServiceImpl implements AgendaBoService {
59  
60      private BusinessObjectService businessObjectService;
61      private CriteriaLookupService criteriaLookupService;
62      private KrmsAttributeDefinitionService attributeDefinitionService;
63      private SequenceAccessorService sequenceAccessorService;
64  
65      // used for converting lists of BOs to model objects
66      private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition =
67              new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() {
68                  public AgendaItemDefinition transform(AgendaItemBo input) {
69                      return AgendaItemBo.to(input);
70                  };
71              };
72  
73      // used for converting lists of BOs to model objects
74      private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition =
75              new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() {
76                  public AgendaDefinition transform(AgendaBo input) {
77                      return AgendaBo.to(input);
78                  };
79              };
80  
81  
82      /**
83       * This overridden method creates a KRMS Agenda in the repository
84       */
85      @Override
86      public AgendaDefinition createAgenda(AgendaDefinition agenda) {
87          if (agenda == null){
88              throw new RiceIllegalArgumentException("agenda is null");
89          }
90          final String nameKey = agenda.getName();
91          final String contextId = agenda.getContextId();
92          final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId);
93          if (existing != null){
94              throw new IllegalStateException("the agenda to create already exists: " + agenda);
95          }
96  
97          AgendaBo agendaBo = from(agenda);
98          businessObjectService.save(agendaBo);
99          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 }