View Javadoc

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