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.kns.service.KNSServiceLocator;
41  import org.kuali.rice.krad.service.BusinessObjectService;
42  import org.kuali.rice.krad.service.KRADServiceLocator;
43  import org.kuali.rice.krad.service.SequenceAccessorService;
44  import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
45  import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
46  import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
47  import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
48  import org.springframework.util.CollectionUtils;
49  
50  import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
51  
52  /**
53   * Implementation of the interface for accessing KRMS repository Agenda related
54   * business objects. 
55   *
56   * @author Kuali Rice Team (rice.collab@kuali.org)
57   *
58   */
59  public final class AgendaBoServiceImpl implements AgendaBoService {
60  
61      // TODO: deal with active flag
62  
63      private BusinessObjectService businessObjectService;
64      private CriteriaLookupService criteriaLookupService;
65      private KrmsAttributeDefinitionService attributeDefinitionService;
66      private SequenceAccessorService sequenceAccessorService;
67  
68      // used for converting lists of BOs to model objects
69      private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition =
70              new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() {
71                  public AgendaItemDefinition transform(AgendaItemBo input) {
72                      return AgendaItemBo.to(input);
73                  };
74              };
75  
76      // used for converting lists of BOs to model objects
77      private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition =
78              new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() {
79                  public AgendaDefinition transform(AgendaBo input) {
80                      return AgendaBo.to(input);
81                  };
82              };
83  
84  
85      /**
86       * This overridden method creates a KRMS Agenda in the repository
87       */
88      @Override
89      public AgendaDefinition createAgenda(AgendaDefinition agenda) {
90          if (agenda == null){
91              throw new RiceIllegalArgumentException("agenda is null");
92          }
93          final String nameKey = agenda.getName();
94          final String contextId = agenda.getContextId();
95          final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId);
96          if (existing != null){
97              throw new IllegalStateException("the agenda to create already exists: " + agenda);
98          }
99  
100         AgendaBo agendaBo = from(agenda);
101         businessObjectService.save(agendaBo);
102         return to(agendaBo);
103     }
104 
105     /**
106      * This overridden method updates an existing Agenda in the repository
107      */
108     @Override
109     public void updateAgenda(AgendaDefinition agenda) {
110         if (agenda == null){
111             throw new RiceIllegalArgumentException("agenda is null");
112         }
113 
114         // must already exist to be able to update
115         final String agendaIdKey = agenda.getId();
116         final AgendaBo existing = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaIdKey);
117         if (existing == null) {
118             throw new IllegalStateException("the agenda does not exist: " + agenda);
119         }
120         final AgendaDefinition toUpdate;
121         if (existing.getId().equals(agenda.getId())) {
122             toUpdate = agenda;
123         } else {
124             // if passed in id does not match existing id, correct it
125             final AgendaDefinition.Builder builder = AgendaDefinition.Builder.create(agenda);
126             builder.setId(existing.getId());
127             toUpdate = builder.build();
128         }
129 
130         // copy all updateable fields to bo
131         AgendaBo boToUpdate = from(toUpdate);
132 
133         // delete any old, existing attributes
134         Map<String,String> fields = new HashMap<String,String>(1);
135         fields.put(PropertyNames.Agenda.AGENDA_ID, toUpdate.getId());
136         businessObjectService.deleteMatching(AgendaAttributeBo.class, fields);
137 
138         // update new agenda and create new attributes
139         businessObjectService.save(boToUpdate);
140     }
141 
142     @Override
143     public void deleteAgenda(String agendaId) {
144         if (agendaId == null){ throw new RiceIllegalArgumentException("agendaId is null"); }
145         final AgendaDefinition existing = getAgendaByAgendaId(agendaId);
146         if (existing == null){ throw new IllegalStateException("the Agenda to delete does not exists: " + agendaId);}
147         businessObjectService.delete(from(existing));
148     }
149 
150     /**
151      * This overridden method retrieves an Agenda from the repository
152      */
153     @Override
154     public AgendaDefinition getAgendaByAgendaId(String agendaId) {
155         if (StringUtils.isBlank(agendaId)){
156             throw new RiceIllegalArgumentException("agenda id is null");
157         }
158         AgendaBo bo = businessObjectService.findBySinglePrimaryKey(AgendaBo.class, agendaId);
159         return to(bo);
160     }
161 
162     /**
163      * This overridden method retrieves an agenda from the repository
164      */
165     @Override
166     public AgendaDefinition getAgendaByNameAndContextId(String name, String contextId) {
167         if (StringUtils.isBlank(name)) {
168             throw new RiceIllegalArgumentException("name is blank");
169         }
170         if (StringUtils.isBlank(contextId)) {
171             throw new RiceIllegalArgumentException("contextId is blank");
172         }
173 
174         final Map<String, Object> map = new HashMap<String, Object>();
175         map.put("name", name);
176         map.put("contextId", contextId);
177 
178         AgendaBo myAgenda = businessObjectService.findByPrimaryKey(AgendaBo.class, Collections.unmodifiableMap(map));
179         return to(myAgenda);
180     }
181 
182     /**
183      * This overridden method retrieves a set of agendas from the repository
184      */
185     @Override
186     public List<AgendaDefinition> getAgendasByContextId(String contextId) {
187         if (StringUtils.isBlank(contextId)){
188             throw new RiceIllegalArgumentException("context ID is null or blank");
189         }
190         final Map<String, Object> map = new HashMap<String, Object>();
191         map.put("contextId", contextId);
192         List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map);
193         return convertAgendaBosToImmutables(bos);
194     }
195 
196     /**
197      * This overridden method creates a new Agenda in the repository
198      */
199     @Override
200     public AgendaItemDefinition createAgendaItem(AgendaItemDefinition agendaItem) {
201         if (agendaItem == null){
202             throw new RiceIllegalArgumentException("agendaItem is null");
203         }
204         if (agendaItem.getId() != null){
205             final AgendaDefinition existing = getAgendaByAgendaId(agendaItem.getId());
206             if (existing != null){
207                 throw new IllegalStateException("the agendaItem to create already exists: " + agendaItem);
208             }
209         }
210 
211         AgendaItemBo bo = AgendaItemBo.from(agendaItem);
212         businessObjectService.save(bo);
213         return AgendaItemBo.to(bo);
214     }
215 
216     /**
217      * This overridden method updates an existing Agenda in the repository
218      */
219     @Override
220     public void updateAgendaItem(AgendaItemDefinition agendaItem) {
221         if (agendaItem == null){
222             throw new RiceIllegalArgumentException("agendaItem is null");
223         }
224         final String agendaItemIdKey = agendaItem.getId();
225         final AgendaItemDefinition existing = getAgendaItemById(agendaItemIdKey);
226         if (existing == null) {
227             throw new IllegalStateException("the agenda item does not exist: " + agendaItem);
228         }
229         final AgendaItemDefinition toUpdate;
230         if (existing.getId().equals(agendaItem.getId())) {
231             toUpdate = agendaItem;
232         } else {
233             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
234             builder.setId(existing.getId());
235             toUpdate = builder.build();
236         }
237 
238         businessObjectService.save(AgendaItemBo.from(toUpdate));
239     }
240 
241     /**
242      * This overridden method adds a new AgendaItemDefinition to the repository
243      */
244     @Override
245     public void addAgendaItem(AgendaItemDefinition agendaItem, String parentId, Boolean position) {
246         if (agendaItem == null){
247             throw new RiceIllegalArgumentException("agendaItem is null");
248         }
249         AgendaItemDefinition parent = null;
250         if (parentId != null){
251             parent = getAgendaItemById(parentId);
252             if (parent == null){
253                 throw new IllegalStateException("parent agendaItem does not exist in repository. parentId = " + parentId);
254             }
255         }
256         // create new AgendaItemDefinition
257         final AgendaItemDefinition toCreate;
258         if (agendaItem.getId() == null) {
259             SequenceAccessorService sas = getSequenceAccessorService();
260             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
261             final String newId =sas.getNextAvailableSequenceNumber(
262                     "KRMS_AGENDA_ITM_S", AgendaItemBo.class).toString();
263             builder.setId(newId);
264             toCreate = builder.build();
265         } else {
266             toCreate = agendaItem;
267         }
268         createAgendaItem(toCreate);
269 
270         // link it to it's parent (for whenTrue/whenFalse, sibling for always
271         if (parentId != null) {
272             final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(parent);
273             if (position == null){
274                 builder.setAlwaysId( toCreate.getId() );
275             } else if (position.booleanValue()){
276                 builder.setWhenTrueId( toCreate.getId() );
277             } else if (!position.booleanValue()){
278                 builder.setWhenFalseId( toCreate.getId() );
279             }
280             final AgendaItemDefinition parentToUpdate = builder.build();
281             updateAgendaItem( parentToUpdate );
282         }
283     }
284 
285     /**
286      * This overridden method retrieves an AgendaItemDefinition from the repository
287      */
288     @Override
289     public AgendaItemDefinition getAgendaItemById(String id) {
290         if (StringUtils.isBlank(id)){
291             throw new RiceIllegalArgumentException("agenda item id is null or blank");
292         }
293         AgendaItemBo bo = businessObjectService.findBySinglePrimaryKey(AgendaItemBo.class, id);
294         return AgendaItemBo.to(bo);
295     }
296 
297     @Override
298     public List<AgendaItemDefinition> getAgendaItemsByAgendaId(String agendaId) {
299         if (StringUtils.isBlank(agendaId)){
300             throw new RiceIllegalArgumentException("agenda id is null or null");
301         }
302         List<AgendaItemDefinition> results = null;
303 
304         Collection<AgendaItemBo> bos = businessObjectService.findMatching(AgendaItemBo.class, Collections.singletonMap("agendaId", agendaId));
305 
306         if (CollectionUtils.isEmpty(bos)) {
307             results = Collections.emptyList();
308         } else {
309             results = Collections.unmodifiableList(ModelObjectUtils.transform(bos, toAgendaItemDefinition));
310         }
311 
312         return results;
313     }
314 
315     @Override
316     public List<AgendaDefinition> getAgendasByType(String typeId) throws RiceIllegalArgumentException {
317         if (StringUtils.isBlank(typeId)){
318             throw new RiceIllegalArgumentException("type ID is null or blank");
319         }
320         final Map<String, Object> map = new HashMap<String, Object>();
321         map.put("typeId", typeId);
322         List<AgendaBo> bos = (List<AgendaBo>) businessObjectService.findMatching(AgendaBo.class, map);
323         return convertAgendaBosToImmutables(bos);
324     }
325 
326     @Override
327     public List<AgendaDefinition> getAgendasByTypeAndContext(String typeId,
328             String contextId) throws RiceIllegalArgumentException {
329         if (StringUtils.isBlank(typeId)){
330             throw new RiceIllegalArgumentException("type ID is null or blank");
331         }
332         if (StringUtils.isBlank(contextId)){
333             throw new RiceIllegalArgumentException("context ID is null or blank");
334         }
335         final Map<String, Object> map = new HashMap<String, Object>();
336         map.put("typeId", typeId);
337         map.put("contextId", contextId);
338         Collection<AgendaBo> bos = businessObjectService.findMatching(AgendaBo.class, map);
339         return convertAgendaBosToImmutables(bos);
340     }
341 
342     @Override
343     public List<AgendaItemDefinition> getAgendaItemsByType(String typeId) throws RiceIllegalArgumentException {
344         return findAgendaItemsForAgendas(getAgendasByType(typeId));
345     }
346 
347     @Override
348     public List<AgendaItemDefinition> getAgendaItemsByContext(String contextId) throws RiceIllegalArgumentException {
349         return findAgendaItemsForAgendas(getAgendasByContextId(contextId));
350     }
351 
352     @Override
353     public List<AgendaItemDefinition> getAgendaItemsByTypeAndContext(String typeId,
354             String contextId) throws RiceIllegalArgumentException {
355         return findAgendaItemsForAgendas(getAgendasByTypeAndContext(typeId, contextId));
356     }
357 
358     @Override
359     public void deleteAgendaItem(String agendaItemId) throws RiceIllegalArgumentException {
360         if (StringUtils.isBlank(agendaItemId)) {
361             throw new RiceIllegalArgumentException("agendaItemId must not be blank or null");
362         }
363 
364         businessObjectService.deleteMatching(AgendaItemBo.class, Collections.singletonMap("id", agendaItemId));
365     }
366 
367     private List<AgendaItemDefinition> findAgendaItemsForAgendas(List<AgendaDefinition> agendaDefinitions) {
368         List<AgendaItemDefinition> results = null;
369 
370         if (!CollectionUtils.isEmpty(agendaDefinitions)) {
371             List<AgendaItemBo> boResults = new ArrayList<AgendaItemBo>(agendaDefinitions.size());
372 
373             List<String> agendaIds = new ArrayList<String>(20);
374             for (AgendaDefinition agendaDefinition : agendaDefinitions) {
375                 agendaIds.add(agendaDefinition.getId());
376 
377                 if (agendaIds.size() == 20) {
378                     // fetch batch
379 
380                     Predicate predicate = in("agendaId", agendaIds.toArray());
381                     QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate);
382                     GenericQueryResults<AgendaItemBo> batch = criteriaLookupService.lookup(AgendaItemBo.class, criteria);
383 
384                     boResults.addAll(batch.getResults());
385 
386                     // reset agendaIds
387                     agendaIds.clear();
388                 }
389             }
390 
391             if (agendaIds.size() > 0) {
392                 Predicate predicate = in("agendaId", agendaIds.toArray());
393                 QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate);
394                 GenericQueryResults<AgendaItemBo> batch = criteriaLookupService.lookup(AgendaItemBo.class, criteria);
395 
396                 boResults.addAll(batch.getResults());
397             }
398 
399             results = Collections.unmodifiableList(ModelObjectUtils.transform(boResults, toAgendaItemDefinition));
400         } else {
401             results = Collections.emptyList();
402         }
403 
404         return results;
405     }
406 
407     public CriteriaLookupService getCriteriaLookupService() {
408         if (criteriaLookupService == null) {
409             criteriaLookupService = KrmsRepositoryServiceLocator.getCriteriaLookupService();
410         }
411         return criteriaLookupService;
412     }
413 
414     public void setCriteriaLookupService(CriteriaLookupService criteriaLookupService) {
415         this.criteriaLookupService = criteriaLookupService;
416     }
417 
418     /**
419      * Sets the businessObjectService attribute value.
420      *
421      * @param businessObjectService The businessObjectService to set.
422      */
423     public void setBusinessObjectService(final BusinessObjectService businessObjectService) {
424         this.businessObjectService = businessObjectService;
425     }
426 
427     protected BusinessObjectService getBusinessObjectService() {
428         if ( businessObjectService == null ) {
429             businessObjectService = KNSServiceLocator.getBusinessObjectService();
430         }
431         return businessObjectService;
432     }
433 
434     /**
435      * Sets the sequenceAccessorService attribute value.
436      *
437      * @param sequenceAccessorService The sequenceAccessorService to set.
438      */
439     public void setSequenceAccessorService(final SequenceAccessorService sequenceAccessorService) {
440         this.sequenceAccessorService = sequenceAccessorService;
441     }
442 
443     protected SequenceAccessorService getSequenceAccessorService() {
444         if ( sequenceAccessorService == null ) {
445             sequenceAccessorService = KRADServiceLocator.getSequenceAccessorService();
446         }
447         return sequenceAccessorService;
448     }
449 
450     protected KrmsAttributeDefinitionService getAttributeDefinitionService() {
451         if (attributeDefinitionService == null) {
452             attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
453         }
454         return attributeDefinitionService;
455     }
456 
457     public void setAttributeDefinitionService(KrmsAttributeDefinitionService attributeDefinitionService) {
458         this.attributeDefinitionService = attributeDefinitionService;
459     }
460 
461     /**
462      * Converts a Set<AgendaBo> to an Unmodifiable Set<Agenda>
463      *
464      * @param agendaBos a mutable Set<AgendaBo> to made completely immutable.
465      * @return An unmodifiable Set<Agenda>
466      */
467     public List<AgendaDefinition> convertAgendaBosToImmutables(final Collection<AgendaBo> agendaBos) {
468         if (CollectionUtils.isEmpty(agendaBos)) {
469             return Collections.emptyList();
470         }
471         return Collections.unmodifiableList(ModelObjectUtils.transform(agendaBos, toAgendaDefinition));
472     }
473 
474     /**
475      * Converts a mutable bo to it's immutable counterpart
476      * @param bo the mutable business object
477      * @return the immutable object
478      */
479     @Override
480     public AgendaDefinition to(AgendaBo bo) {
481         if (bo == null) { return null; }
482         return org.kuali.rice.krms.api.repository.agenda.AgendaDefinition.Builder.create(bo).build();
483     }
484 
485 
486     /**
487      * Converts a immutable object to it's mutable bo counterpart
488      * @param im immutable object
489      * @return the mutable bo
490      */
491     @Override
492     public AgendaBo from(AgendaDefinition im) {
493         if (im == null) { return null; }
494 
495         AgendaBo bo = new AgendaBo();
496         bo.setId(im.getId());
497         bo.setName( im.getName() );
498         bo.setTypeId( im.getTypeId() );
499         bo.setContextId( im.getContextId() );
500         bo.setFirstItemId( im.getFirstItemId() );
501         bo.setVersionNumber( im.getVersionNumber() );
502         bo.setActive(im.isActive());
503         Set<AgendaAttributeBo> attributes = buildAgendaAttributeBo(im);
504 
505         bo.setAttributeBos(attributes);
506 
507         return bo;
508     }
509 
510     private Set<AgendaAttributeBo> buildAgendaAttributeBo(AgendaDefinition im) {
511         Set<AgendaAttributeBo> attributes = new HashSet<AgendaAttributeBo>();
512 
513         // build a map from attribute name to definition
514         Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
515 
516         List<KrmsAttributeDefinition> attributeDefinitions =
517                 getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId());
518 
519         for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
520             attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
521         }
522 
523         // for each entry, build an AgendaAttributeBo and add it to the set
524         for (Entry<String,String> entry  : im.getAttributes().entrySet()){
525             KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey());
526 
527             if (attrDef != null) {
528                 AgendaAttributeBo attributeBo = new AgendaAttributeBo();
529                 attributeBo.setAgendaId( im.getId() );
530                 attributeBo.setAttributeDefinitionId(attrDef.getId());
531                 attributeBo.setValue(entry.getValue());
532                 attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef));
533                 attributes.add( attributeBo );
534             } else {
535                 throw new RiceIllegalStateException("there is no attribute definition with the name '" +
536                         entry.getKey() + "' that is valid for the agenda type with id = '" + im.getTypeId() +"'");
537             }
538         }
539         return attributes;
540     }
541 
542 }