001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.krms.impl.repository;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.kuali.rice.core.api.criteria.Predicate;
021import org.kuali.rice.core.api.criteria.QueryByCriteria;
022import org.kuali.rice.core.api.criteria.QueryResults;
023import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
024import org.kuali.rice.krad.data.DataObjectService;
025import org.kuali.rice.krad.service.KRADServiceLocator;
026import org.kuali.rice.krms.api.repository.RuleRepositoryService;
027import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
028import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry;
029import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry;
030import org.kuali.rice.krms.api.repository.context.ContextDefinition;
031import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria;
032import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
033import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
034
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.List;
038import java.util.Map;
039
040import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
041
042/**
043 *
044 */
045public class RuleRepositoryServiceImpl implements RuleRepositoryService {
046
047    protected DataObjectService dataObjectService;
048
049    @Override
050    public ContextDefinition selectContext(ContextSelectionCriteria contextSelectionCriteria) {
051        if (contextSelectionCriteria == null) {
052            throw new RiceIllegalArgumentException("selection criteria is null");
053        }
054        if (StringUtils.isBlank(contextSelectionCriteria.getNamespaceCode())) {
055            throw new RiceIllegalArgumentException("selection criteria namespaceCode is null or blank");
056        }
057
058        QueryByCriteria queryCriteria = buildQuery(contextSelectionCriteria);
059        QueryResults<ContextBo> results = getDataObjectService().findMatching(ContextBo.class, queryCriteria);
060
061        List<ContextBo> resultBos = results.getResults();
062
063        //assuming 1 ?
064        ContextDefinition result = null;
065        if (!CollectionUtils.isEmpty(resultBos)) {
066            if (resultBos.size() == 1) {
067                ContextBo bo = resultBos.iterator().next();
068                return ContextBo.to(bo);
069            } else {
070                throw new RiceIllegalArgumentException(
071                        "Ambiguous context qualifiers, can not select more than one context.");
072            }
073        }
074
075        return result;
076    }
077
078    @Override
079    public AgendaTreeDefinition getAgendaTree(String agendaId) {
080        if (StringUtils.isBlank(agendaId)){
081            throw new RiceIllegalArgumentException("agenda id is null or blank");
082        }
083
084        // Get agenda items from db, then build up agenda tree structure
085        AgendaBo agendaBo = getDataObjectService().find(AgendaBo.class, agendaId);
086
087        if (agendaBo == null) {
088            return null;
089        }
090
091        String agendaItemId = agendaBo.getFirstItemId();
092
093        // walk thru the agenda items, building an agenda tree definition Builder along the way
094        AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
095        myBuilder.setAgendaId( agendaId );
096
097        if (agendaItemId != null) {
098            myBuilder = walkAgendaItemTree(agendaItemId, myBuilder);
099        }
100
101        // build the agenda tree and return it
102        return myBuilder.build();
103    }
104
105    @Override
106    public List<AgendaTreeDefinition> getAgendaTrees(List<String> agendaIds) {
107        List<AgendaTreeDefinition> agendaTrees = new ArrayList<AgendaTreeDefinition>();
108
109        if (agendaIds == null) {
110            return agendaTrees;
111        }
112
113        for (String agendaId : agendaIds){
114            if (getAgendaTree(agendaId) != null) {
115                agendaTrees.add( getAgendaTree(agendaId) );
116            }
117        }
118        return Collections.unmodifiableList(agendaTrees);
119    }
120
121    @Override
122    public RuleDefinition getRule(String ruleId) {
123        if (StringUtils.isBlank(ruleId)){
124            return null;
125        }
126
127        RuleBo bo = getDataObjectService().find(RuleBo.class, ruleId);
128
129        return RuleBo.to(bo);
130    }
131
132    @Override
133    public List<RuleDefinition> getRules(List<String> ruleIds) {
134        if (ruleIds == null) throw new RiceIllegalArgumentException("ruleIds must not be null");
135
136        // Fetch BOs
137        List<RuleBo> bos = null;
138        if (ruleIds.size() == 0) {
139            bos = Collections.emptyList();
140        } else {
141            QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();
142            List<Predicate> pList = new ArrayList<Predicate>();
143            qBuilder.setPredicates(in("id", ruleIds.toArray()));
144            QueryResults<RuleBo> results = getDataObjectService().findMatching(RuleBo.class, qBuilder.build());
145
146            bos = results.getResults();
147        }
148
149        // Translate BOs
150        ArrayList<RuleDefinition> rules = new ArrayList<RuleDefinition>();
151
152        for (RuleBo bo : bos) {
153            RuleDefinition rule = RuleBo.to(bo);
154            rules.add(rule);
155        }
156
157        return Collections.unmodifiableList(rules);
158    }
159
160    /**
161     * Recursive method to create AgendaTreeDefinition builder
162     *
163     *
164     */
165    private AgendaTreeDefinition.Builder walkAgendaItemTree(String agendaItemId, AgendaTreeDefinition.Builder builder){
166        //TODO: prevent circular, endless looping
167        if (StringUtils.isBlank(agendaItemId)){
168            return null;
169        }
170        // Get AgendaItemDefinition Business Object from database
171        // NOTE: if we read agendaItem one at a time from db.   Could consider linking in OJB and getting all at once
172        AgendaItemBo agendaItemBo = getDataObjectService().find(AgendaItemBo.class, agendaItemId);
173
174        // If Rule
175        // TODO: validate that only either rule or subagenda, not both
176        if (!StringUtils.isBlank( agendaItemBo.getRuleId() )){
177            // setup new rule entry builder
178            AgendaTreeRuleEntry.Builder ruleEntryBuilder = AgendaTreeRuleEntry.Builder
179                    .create(agendaItemBo.getId(), agendaItemBo.getRuleId());
180            ruleEntryBuilder.setRuleId( agendaItemBo.getRuleId() );
181            ruleEntryBuilder.setAgendaItemId( agendaItemBo.getId() );
182
183            if (agendaItemBo.getWhenTrueId() != null){
184                // Go follow the true branch, creating AgendaTreeDefinintion Builder for the
185                // true branch level
186                AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
187                myBuilder.setAgendaId( agendaItemBo.getAgendaId() );
188                ruleEntryBuilder.setIfTrue( walkAgendaItemTree(agendaItemBo.getWhenTrueId(),myBuilder));
189            }
190
191            if (agendaItemBo.getWhenFalseId() != null){
192                // Go follow the false branch, creating AgendaTreeDefinintion Builder
193                AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
194                myBuilder.setAgendaId( agendaItemBo.getAgendaId() );
195                ruleEntryBuilder.setIfFalse( walkAgendaItemTree(agendaItemBo.getWhenFalseId(), myBuilder));
196            }
197
198            // Build the Rule Entry and add it to the AgendaTreeDefinition builder
199            builder.addRuleEntry( ruleEntryBuilder.build() );
200        }
201
202        // if SubAgenda and a sub agenda tree entry
203        if (!StringUtils.isBlank(agendaItemBo.getSubAgendaId())) {
204            AgendaTreeSubAgendaEntry.Builder subAgendaEntryBuilder =
205                    AgendaTreeSubAgendaEntry.Builder.create(agendaItemBo.getId(), agendaItemBo.getSubAgendaId());
206            builder.addSubAgendaEntry( subAgendaEntryBuilder.build() );
207        }
208
209        // if this agenda item has an "After Id", follow that branch
210        if (!StringUtils.isBlank( agendaItemBo.getAlwaysId() )){
211            builder = walkAgendaItemTree( agendaItemBo.getAlwaysId(), builder);
212
213        }
214
215        return builder;
216    }
217
218    /**
219     *
220     * This method converts a {@link ContextSelectionCriteria} object into a
221     * {@link QueryByCriteria} object with the proper predicates for AttributeBo properties.
222     *
223     * @param selectionCriteria
224     * @return
225     */
226    private QueryByCriteria buildQuery( ContextSelectionCriteria selectionCriteria ){
227        Predicate p;
228        QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();
229
230        List<Predicate> pList = new ArrayList<Predicate>();
231
232        if (selectionCriteria.getNamespaceCode() != null){
233            p = equal(PropertyNames.Context.NAMESPACE, selectionCriteria.getNamespaceCode());
234            pList.add(p);
235        }
236        if (selectionCriteria.getName() != null){
237            p = equal(PropertyNames.Context.NAME, selectionCriteria.getName());
238            pList.add(p);
239        }
240
241        if (selectionCriteria.getContextQualifiers() != null){
242            for (Map.Entry<String, String> entry : selectionCriteria.getContextQualifiers().entrySet()){
243                p = and(equal(PropertyNames.Context.ATTRIBUTE_BOS
244                        + "." + PropertyNames.BaseAttribute.ATTRIBUTE_DEFINITION
245                        + "." + PropertyNames.KrmsAttributeDefinition.NAME, entry.getKey()),
246                        equal(PropertyNames.Context.ATTRIBUTE_BOS
247                                + "." + PropertyNames.BaseAttribute.VALUE, entry.getValue()));
248                pList.add(p);
249            }
250        }
251
252        Predicate[] preds = new Predicate[pList.size()];
253        pList.toArray(preds);
254        qBuilder.setPredicates(and(preds));
255
256        return qBuilder.build();
257    }
258
259    public void setDataObjectService(DataObjectService dataObjectService) {
260        this.dataObjectService = dataObjectService;
261    }
262
263    public DataObjectService getDataObjectService() {
264        if (dataObjectService == null) {
265            dataObjectService = KRADServiceLocator.getDataObjectService();
266        }
267
268        return dataObjectService;
269    }
270}