View Javadoc

1   /**
2    * Copyright 2005-2012 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 AgendaBoServiceImpl implements AgendaBoService {
59  
60      // TODO: deal with active flag
61  
62      private BusinessObjectService businessObjectService;
63      private CriteriaLookupService criteriaLookupService;
64      private KrmsAttributeDefinitionService attributeDefinitionService;
65      private SequenceAccessorService sequenceAccessorService;
66  
67      // used for converting lists of BOs to model objects
68      private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition =
69              new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() {
70                  public AgendaItemDefinition transform(AgendaItemBo input) {
71                      return AgendaItemBo.to(input);
72                  };
73              };
74  
75      // used for converting lists of BOs to model objects
76      private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition =
77              new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() {
78                  public AgendaDefinition transform(AgendaBo input) {
79                      return AgendaBo.to(input);
80                  };
81              };
82  
83  
84      /**
85       * This overridden method creates a KRMS Agenda in the repository
86       */
87      @Override
88      public AgendaDefinition createAgenda(AgendaDefinition agenda) {
89          if (agenda == null){
90              throw new RiceIllegalArgumentException("agenda is null");
91          }
92          final String nameKey = agenda.getName();
93          final String contextId = agenda.getContextId();
94          final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId);
95          if (existing != null){
96              throw new IllegalStateException("the agenda to create already exists: " + agenda);
97          }
98  
99          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 }