001package org.kuali.rice.krms.impl.repository;
002
003import org.apache.commons.lang.StringUtils;
004import org.kuali.rice.core.api.criteria.Predicate;
005import org.kuali.rice.core.api.criteria.QueryByCriteria;
006import org.kuali.rice.core.api.criteria.QueryResults;
007import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
008import org.kuali.rice.core.api.exception.RiceIllegalStateException;
009import org.kuali.rice.core.api.mo.ModelObjectUtils;
010import org.kuali.rice.krad.data.DataObjectService;
011import org.kuali.rice.krad.data.PersistenceOption;
012import org.kuali.rice.krad.service.KRADServiceLocator;
013import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
014import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
015import org.kuali.rice.krms.api.repository.type.KrmsAttributeDefinition;
016import org.springframework.util.CollectionUtils;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
028import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.deleteMatching;
029import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findMatching;
030import static org.kuali.rice.krms.impl.repository.BusinessObjectServiceMigrationUtils.findSingleMatching;
031
032/**
033 * Created by SW Genis on 2014/05/06.
034 */
035public class KSAgendaBoServiceImpl implements AgendaBoService {
036
037    // TODO: deal with active flag
038
039    private DataObjectService dataObjectService;
040    private KrmsAttributeDefinitionService attributeDefinitionService;
041
042    // used for converting lists of BOs to model objects
043    private static final ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition> toAgendaItemDefinition =
044            new ModelObjectUtils.Transformer<AgendaItemBo, AgendaItemDefinition>() {
045                public AgendaItemDefinition transform(AgendaItemBo input) {
046                    return AgendaItemBo.to(input);
047                };
048            };
049
050    // used for converting lists of BOs to model objects
051    private static final ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition> toAgendaDefinition =
052            new ModelObjectUtils.Transformer<AgendaBo, AgendaDefinition>() {
053                public AgendaDefinition transform(AgendaBo input) {
054                    return AgendaBo.to(input);
055                };
056            };
057
058
059    /**
060     * This overridden method creates a KRMS Agenda in the repository
061     */
062    @Override
063    public AgendaDefinition createAgenda(AgendaDefinition agenda) {
064        if (agenda == null){
065            throw new RiceIllegalArgumentException("agenda is null");
066        }
067        final String nameKey = agenda.getName();
068        final String contextId = agenda.getContextId();
069        final AgendaDefinition existing = getAgendaByNameAndContextId(nameKey, contextId);
070        if (existing != null){
071            throw new IllegalStateException("the agenda to create already exists: " + agenda);
072        }
073
074        AgendaBo agendaBo = from(agenda);
075        agendaBo = dataObjectService.save(agendaBo, PersistenceOption.FLUSH);
076        return to(agendaBo);
077    }
078
079    /**
080     * This overridden method updates an existing Agenda in the repository
081     */
082    @Override
083    public void updateAgenda(AgendaDefinition agenda) {
084        if (agenda == null){
085            throw new RiceIllegalArgumentException("agenda is null");
086        }
087
088        // must already exist to be able to update
089        final String agendaIdKey = agenda.getId();
090        final AgendaBo existing = dataObjectService.find(AgendaBo.class, agendaIdKey);
091        if (existing == null) {
092            throw new IllegalStateException("the agenda does not exist: " + agenda);
093        }
094        final AgendaDefinition toUpdate;
095        if (existing.getId().equals(agenda.getId())) {
096            toUpdate = agenda;
097        } else {
098            // if passed in id does not match existing id, correct it
099            final AgendaDefinition.Builder builder = AgendaDefinition.Builder.create(agenda);
100            builder.setId(existing.getId());
101            toUpdate = builder.build();
102        }
103
104        // copy all updateable fields to bo
105        AgendaBo boToUpdate = from(toUpdate);
106
107        // move over AgendaBo members that don't get populated from AgendaDefinition
108        boToUpdate.setItems(existing.getItems());
109
110        // delete any old, existing attributes
111        Map<String,String> fields = new HashMap<String,String>(1);
112        fields.put("agenda.id", toUpdate.getId());
113        deleteMatching(dataObjectService, AgendaAttributeBo.class, fields);
114
115        // update new agenda and create new attributes
116        dataObjectService.save(boToUpdate, PersistenceOption.FLUSH);
117    }
118
119    @Override
120    public void deleteAgenda(String agendaId) {
121        if (agendaId == null){ throw new RiceIllegalArgumentException("agendaId is null"); }
122        final AgendaBo bo = dataObjectService.find(AgendaBo.class, agendaId);
123        if (bo == null){ throw new IllegalStateException("the Agenda to delete does not exists: " + agendaId);}
124
125        List<AgendaItemDefinition> agendaItems = this.getAgendaItemsByAgendaId(bo.getId());
126        for( AgendaItemDefinition agendaItem : agendaItems) {
127            dataObjectService.delete(AgendaItemBo.from(agendaItem));
128        }
129
130        dataObjectService.delete(bo);
131    }
132
133    /**
134     * This overridden method retrieves an Agenda from the repository
135     */
136    @Override
137    public AgendaDefinition getAgendaByAgendaId(String agendaId) {
138        if (StringUtils.isBlank(agendaId)){
139            throw new RiceIllegalArgumentException("agenda id is null or blank");
140        }
141        AgendaBo bo = dataObjectService.find(AgendaBo.class, agendaId);
142        return to(bo);
143    }
144
145    /**
146     * This overridden method retrieves an agenda from the repository
147     */
148    @Override
149    public AgendaDefinition getAgendaByNameAndContextId(String name, String contextId) {
150        if (StringUtils.isBlank(name)) {
151            throw new RiceIllegalArgumentException("name is blank");
152        }
153        if (StringUtils.isBlank(contextId)) {
154            throw new RiceIllegalArgumentException("contextId is blank");
155        }
156
157        final Map<String, Object> map = new HashMap<String, Object>();
158        map.put("name", name);
159        map.put("contextId", contextId);
160
161        AgendaBo myAgenda = findSingleMatching(dataObjectService, AgendaBo.class, map);
162        return to(myAgenda);
163    }
164
165    /**
166     * This overridden method retrieves a set of agendas from the repository
167     */
168    @Override
169    public List<AgendaDefinition> getAgendasByContextId(String contextId) {
170        if (StringUtils.isBlank(contextId)){
171            throw new RiceIllegalArgumentException("context ID is null or blank");
172        }
173        final Map<String, Object> map = new HashMap<String, Object>();
174        map.put("contextId", contextId);
175        List<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
176
177        return convertAgendaBosToImmutables(bos);
178    }
179
180    /**
181     * This overridden method creates a new Agenda in the repository
182     */
183    @Override
184    public AgendaItemDefinition createAgendaItem(AgendaItemDefinition agendaItem) {
185        if (agendaItem == null){
186            throw new RiceIllegalArgumentException("agendaItem is null");
187        }
188        if (agendaItem.getId() != null){
189            final AgendaDefinition existing = getAgendaByAgendaId(agendaItem.getId());
190            if (existing != null){
191                throw new IllegalStateException("the agendaItem to create already exists: " + agendaItem);
192            }
193        }
194
195        AgendaItemBo bo = AgendaItemBo.from(agendaItem);
196        bo = dataObjectService.save(bo, PersistenceOption.FLUSH);
197        return AgendaItemBo.to(bo);
198    }
199
200    /**
201     * This overridden method updates an existing Agenda in the repository
202     */
203    @Override
204    public void updateAgendaItem(AgendaItemDefinition agendaItem) {
205        if (agendaItem == null){
206            throw new RiceIllegalArgumentException("agendaItem is null");
207        }
208        final String agendaItemIdKey = agendaItem.getId();
209        final AgendaItemDefinition existing = getAgendaItemById(agendaItemIdKey);
210        if (existing == null) {
211            throw new IllegalStateException("the agenda item does not exist: " + agendaItem);
212        }
213        final AgendaItemDefinition toUpdate;
214        if (existing.getId().equals(agendaItem.getId())) {
215            toUpdate = agendaItem;
216        } else {
217            final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
218            builder.setId(existing.getId());
219            toUpdate = builder.build();
220        }
221
222        AgendaItemBo aiBo = AgendaItemBo.from(toUpdate);
223        //updateActionAttributes(aiBo);    KSENROLL-12750 - Don't think this is required with jpa.
224        dataObjectService.save(aiBo, PersistenceOption.FLUSH);
225    }
226
227    // KSENROLL-12750 - Don't think this is required with jpa.
228    /*private void updateActionAttributes(AgendaItemBo aiBo) {
229        if(aiBo.getRule()!=null){
230            updateActionAttributes(aiBo.getRule().getActions());
231        }
232        if(aiBo.getWhenTrue()!=null){
233            updateActionAttributes(aiBo.getWhenTrue());
234        }
235        if(aiBo.getWhenFalse()!=null){
236            updateActionAttributes(aiBo.getWhenFalse());
237        }
238        if(aiBo.getAlways()!=null){
239            updateActionAttributes(aiBo.getAlways());
240        }
241    }
242
243    private void updateActionAttributes(List<ActionBo> actionBos) {
244        for (ActionBo action : actionBos) {
245            for (ActionAttributeBo aa : action.getAttributeBos()) {
246                Map<String, Object> map = new HashMap<String, Object>();
247                map.put("actionId", action.getId());
248                Collection<ActionAttributeBo> aaBos = findMatching(dataObjectService, ActionAttributeBo.class, map);
249
250                for (ActionAttributeBo aaBo : aaBos) {
251                    if (StringUtils.equals(aaBo.getAttributeDefinitionId(), aa.getAttributeDefinitionId())) {
252                        aa.setId(aaBo.getId());
253                        aa.setVersionNumber(aaBo.getVersionNumber());
254                    }
255                }
256            }
257        }
258    }*/
259
260    /**
261     * This overridden method adds a new AgendaItemDefinition to the repository
262     */
263    @Override
264    public void addAgendaItem(AgendaItemDefinition agendaItem, String parentId, Boolean position) {
265        if (agendaItem == null){
266            throw new RiceIllegalArgumentException("agendaItem is null");
267        }
268        AgendaItemDefinition parent = null;
269        if (parentId != null){
270            parent = getAgendaItemById(parentId);
271            if (parent == null){
272                throw new IllegalStateException("parent agendaItem does not exist in repository. parentId = " + parentId);
273            }
274        }
275        // create new AgendaItemDefinition
276        final AgendaItemDefinition toCreate;
277        if (agendaItem.getId() == null) {
278            final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(agendaItem);
279            builder.setId(AgendaItemBo.agendaItemIdIncrementer.getNewId());
280            toCreate = builder.build();
281        } else {
282            toCreate = agendaItem;
283        }
284        createAgendaItem(toCreate);
285
286        // link it to it's parent (for whenTrue/whenFalse, sibling for always
287        if (parentId != null) {
288            final AgendaItemDefinition.Builder builder = AgendaItemDefinition.Builder.create(parent);
289            if (position == null){
290                builder.setAlwaysId( toCreate.getId() );
291            } else if (position.booleanValue()){
292                builder.setWhenTrueId( toCreate.getId() );
293            } else if (!position.booleanValue()){
294                builder.setWhenFalseId( toCreate.getId() );
295            }
296            final AgendaItemDefinition parentToUpdate = builder.build();
297            updateAgendaItem( parentToUpdate );
298        }
299    }
300
301    /**
302     * This overridden method retrieves an AgendaItemDefinition from the repository
303     */
304    @Override
305    public AgendaItemDefinition getAgendaItemById(String id) {
306        if (StringUtils.isBlank(id)){
307            throw new RiceIllegalArgumentException("agenda item id is null or blank");
308        }
309
310        AgendaItemBo bo = dataObjectService.find(AgendaItemBo.class, id);
311
312        return AgendaItemBo.to(bo);
313    }
314
315    @Override
316    public List<AgendaItemDefinition> getAgendaItemsByAgendaId(String agendaId) {
317        if (StringUtils.isBlank(agendaId)){
318            throw new RiceIllegalArgumentException("agenda id is null or null");
319        }
320        List<AgendaItemDefinition> results = null;
321
322        Collection<AgendaItemBo> bos = findMatching(dataObjectService, AgendaItemBo.class, Collections.singletonMap(
323                "agendaId", agendaId));
324
325        if (CollectionUtils.isEmpty(bos)) {
326            results = Collections.emptyList();
327        } else {
328            results = Collections.unmodifiableList(ModelObjectUtils.transform(bos, toAgendaItemDefinition));
329        }
330
331        return results;
332    }
333
334    @Override
335    public List<AgendaDefinition> getAgendasByType(String typeId) throws RiceIllegalArgumentException {
336        if (StringUtils.isBlank(typeId)){
337            throw new RiceIllegalArgumentException("type ID is null or blank");
338        }
339
340        final Map<String, Object> map = new HashMap<String, Object>();
341        map.put("typeId", typeId);
342        List<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
343
344        return convertAgendaBosToImmutables(bos);
345    }
346
347    @Override
348    public List<AgendaDefinition> getAgendasByTypeAndContext(String typeId,
349                                                             String contextId) throws RiceIllegalArgumentException {
350        if (StringUtils.isBlank(typeId)){
351            throw new RiceIllegalArgumentException("type ID is null or blank");
352        }
353        if (StringUtils.isBlank(contextId)){
354            throw new RiceIllegalArgumentException("context ID is null or blank");
355        }
356        final Map<String, Object> map = new HashMap<String, Object>();
357        map.put("typeId", typeId);
358        map.put("contextId", contextId);
359        Collection<AgendaBo> bos = findMatching(dataObjectService, AgendaBo.class, map);
360
361        return convertAgendaBosToImmutables(bos);
362    }
363
364    @Override
365    public List<AgendaItemDefinition> getAgendaItemsByType(String typeId) throws RiceIllegalArgumentException {
366        return findAgendaItemsForAgendas(getAgendasByType(typeId));
367    }
368
369    @Override
370    public List<AgendaItemDefinition> getAgendaItemsByContext(String contextId) throws RiceIllegalArgumentException {
371        return findAgendaItemsForAgendas(getAgendasByContextId(contextId));
372    }
373
374    @Override
375    public List<AgendaItemDefinition> getAgendaItemsByTypeAndContext(String typeId,
376                                                                     String contextId) throws RiceIllegalArgumentException {
377        return findAgendaItemsForAgendas(getAgendasByTypeAndContext(typeId, contextId));
378    }
379
380    @Override
381    public void deleteAgendaItem(String agendaItemId) throws RiceIllegalArgumentException {
382        if (StringUtils.isBlank(agendaItemId)) {
383            throw new RiceIllegalArgumentException("agendaItemId must not be blank or null");
384        }
385
386        deleteMatching(dataObjectService, AgendaItemBo.class, Collections.singletonMap("id", agendaItemId));
387    }
388
389    private List<AgendaItemDefinition> findAgendaItemsForAgendas(List<AgendaDefinition> agendaDefinitions) {
390        List<AgendaItemDefinition> results = null;
391
392        if (!CollectionUtils.isEmpty(agendaDefinitions)) {
393            List<AgendaItemBo> boResults = new ArrayList<AgendaItemBo>(agendaDefinitions.size());
394
395            List<String> agendaIds = new ArrayList<String>(20);
396            for (AgendaDefinition agendaDefinition : agendaDefinitions) {
397                agendaIds.add(agendaDefinition.getId());
398
399                if (agendaIds.size() == 20) {
400                    // fetch batch
401
402                    Predicate predicate = in("agendaId", agendaIds.toArray());
403                    QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate);
404                    QueryResults<AgendaItemBo> batch = getDataObjectService().findMatching(AgendaItemBo.class, criteria);
405
406                    boResults.addAll(batch.getResults());
407
408                    // reset agendaIds
409                    agendaIds.clear();
410                }
411            }
412
413            if (agendaIds.size() > 0) {
414                Predicate predicate = in("agendaId", agendaIds.toArray());
415                QueryByCriteria criteria = QueryByCriteria.Builder.fromPredicates(predicate);
416                QueryResults<AgendaItemBo> batch = getDataObjectService().findMatching(AgendaItemBo.class, criteria);
417
418                boResults.addAll(batch.getResults());
419            }
420
421            results = Collections.unmodifiableList(ModelObjectUtils.transform(boResults, toAgendaItemDefinition));
422        } else {
423            results = Collections.emptyList();
424        }
425
426        return results;
427    }
428
429    /**
430     * Sets the dataObjectService attribute value.
431     *
432     * @param dataObjectService The dataObjectService to set.
433     */
434    public void setDataObjectService(final DataObjectService dataObjectService) {
435        this.dataObjectService = dataObjectService;
436    }
437
438    protected DataObjectService getDataObjectService() {
439        if ( dataObjectService == null ) {
440            dataObjectService = KRADServiceLocator.getDataObjectService();
441        }
442        return dataObjectService;
443    }
444
445    protected KrmsAttributeDefinitionService getAttributeDefinitionService() {
446        if (attributeDefinitionService == null) {
447            attributeDefinitionService = KrmsRepositoryServiceLocator.getKrmsAttributeDefinitionService();
448        }
449        return attributeDefinitionService;
450    }
451
452    public void setAttributeDefinitionService(KrmsAttributeDefinitionService attributeDefinitionService) {
453        this.attributeDefinitionService = attributeDefinitionService;
454    }
455
456    /**
457     * Converts a Set<AgendaBo> to an Unmodifiable Set<Agenda>
458     *
459     * @param agendaBos a mutable Set<AgendaBo> to made completely immutable.
460     * @return An unmodifiable Set<Agenda>
461     */
462    public List<AgendaDefinition> convertAgendaBosToImmutables(final Collection<AgendaBo> agendaBos) {
463        if (CollectionUtils.isEmpty(agendaBos)) {
464            return Collections.emptyList();
465        }
466        return Collections.unmodifiableList(ModelObjectUtils.transform(agendaBos, toAgendaDefinition));
467    }
468
469    /**
470     * Converts a mutable bo to it's immutable counterpart
471     * @param bo the mutable business object
472     * @return the immutable object
473     */
474    @Override
475    public AgendaDefinition to(AgendaBo bo) {
476        if (bo == null) { return null; }
477        return org.kuali.rice.krms.api.repository.agenda.AgendaDefinition.Builder.create(bo).build();
478    }
479
480
481    /**
482     * Converts a immutable object to it's mutable bo counterpart
483     * @param im immutable object
484     * @return the mutable bo
485     */
486    @Override
487    public AgendaBo from(AgendaDefinition im) {
488        if (im == null) { return null; }
489
490        AgendaBo bo = new AgendaBo();
491        bo.setId(im.getId());
492        bo.setName( im.getName() );
493        bo.setTypeId( im.getTypeId() );
494        bo.setContextId( im.getContextId() );
495        bo.setFirstItemId( im.getFirstItemId() );
496        bo.setVersionNumber( im.getVersionNumber() );
497        bo.setActive(im.isActive());
498        Set<AgendaAttributeBo> attributes = buildAgendaAttributeBo(im, bo);
499
500        bo.setAttributeBos(attributes);
501
502        return bo;
503    }
504
505    private Set<AgendaAttributeBo> buildAgendaAttributeBo(AgendaDefinition im, AgendaBo agendaBo) {
506        Set<AgendaAttributeBo> attributes = new HashSet<AgendaAttributeBo>();
507
508        // build a map from attribute name to definition
509        Map<String, KrmsAttributeDefinition> attributeDefinitionMap = new HashMap<String, KrmsAttributeDefinition>();
510
511        List<KrmsAttributeDefinition> attributeDefinitions =
512                getAttributeDefinitionService().findAttributeDefinitionsByType(im.getTypeId());
513
514        for (KrmsAttributeDefinition attributeDefinition : attributeDefinitions) {
515            attributeDefinitionMap.put(attributeDefinition.getName(), attributeDefinition);
516        }
517
518        // for each entry, build an AgendaAttributeBo and add it to the set
519        for (Map.Entry<String,String> entry  : im.getAttributes().entrySet()){
520            KrmsAttributeDefinition attrDef = attributeDefinitionMap.get(entry.getKey());
521
522            if (attrDef != null) {
523                AgendaAttributeBo attributeBo = new AgendaAttributeBo();
524                attributeBo.setAgenda(agendaBo);
525                attributeBo.setValue(entry.getValue());
526                attributeBo.setAttributeDefinition(KrmsAttributeDefinitionBo.from(attrDef));
527                attributes.add( attributeBo );
528            } else {
529                throw new RiceIllegalStateException("there is no attribute definition with the name '" +
530                        entry.getKey() + "' that is valid for the agenda type with id = '" + im.getTypeId() +"'");
531            }
532        }
533        return attributes;
534    }
535}