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