001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.krms.impl.repository;
017    
018    import org.apache.commons.lang.StringUtils;
019    import org.kuali.rice.core.api.criteria.CriteriaLookupService;
020    import org.kuali.rice.core.api.criteria.GenericQueryResults;
021    import org.kuali.rice.core.api.criteria.Predicate;
022    import org.kuali.rice.core.api.criteria.QueryByCriteria;
023    import org.kuali.rice.krad.service.BusinessObjectService;
024    import org.kuali.rice.krad.service.KRADServiceLocator;
025    import org.kuali.rice.krms.api.repository.RuleRepositoryService;
026    import org.kuali.rice.krms.api.repository.agenda.AgendaTreeDefinition;
027    import org.kuali.rice.krms.api.repository.agenda.AgendaTreeRuleEntry;
028    import org.kuali.rice.krms.api.repository.agenda.AgendaTreeSubAgendaEntry;
029    import org.kuali.rice.krms.api.repository.context.ContextDefinition;
030    import org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria;
031    import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
032    import org.kuali.rice.krms.impl.util.KrmsImplConstants.PropertyNames;
033    
034    import java.util.ArrayList;
035    import java.util.Collections;
036    import java.util.List;
037    import java.util.Map;
038    
039    import static org.kuali.rice.core.api.criteria.PredicateFactory.*;
040    
041    /**
042     * NOTE: This is a patched version of this class which fixes a bug in Rice 2.0.0 releated to execution of
043     * an agenda with no rules.
044     */
045    public class RuleRepositoryServiceImpl implements RuleRepositoryService {
046        protected BusinessObjectService businessObjectService;
047        private CriteriaLookupService criteriaLookupService;
048            
049            /**
050             * This overridden method ...
051             * 
052             * @see org.kuali.rice.krms.api.repository.RuleRepositoryService#selectContext(org.kuali.rice.krms.api.repository.context.ContextSelectionCriteria)
053             */
054        @Override
055        public ContextDefinition selectContext(
056                    ContextSelectionCriteria contextSelectionCriteria) {
057            if (contextSelectionCriteria == null){
058                    throw new IllegalArgumentException("selection criteria is null");
059            }
060            if (StringUtils.isBlank(contextSelectionCriteria.getNamespaceCode())){
061                    throw new IllegalArgumentException("selection criteria namespaceCode is null or blank");
062            }
063            QueryByCriteria queryCriteria = buildQuery(contextSelectionCriteria);
064            GenericQueryResults<ContextBo> results = getCriteriaLookupService().lookup(ContextBo.class, queryCriteria);
065    
066            List<ContextBo> resultBos = results.getResults();
067    
068            //assuming 1 ?
069            ContextDefinition result = null;
070            if (resultBos != null) {
071                    if (resultBos.size() == 1) {
072                            ContextBo bo = resultBos.iterator().next();
073                            return ContextBo.to(bo);
074                    }
075                    else throw new IllegalArgumentException("ambiguous qualifiers");
076            }
077            return result;
078        }
079    
080            @Override
081            public AgendaTreeDefinition getAgendaTree(String agendaId) {
082                    if (StringUtils.isBlank(agendaId)){
083                    throw new IllegalArgumentException("agenda id is null or blank");
084            }
085                    // Get agenda items from db, then build up agenda tree structure
086                    AgendaBo agendaBo = getBusinessObjectService().findBySinglePrimaryKey(AgendaBo.class, agendaId);
087                    String agendaItemId = agendaBo.getFirstItemId();
088                    
089                    // walk thru the agenda items, building an agenda tree definition Builder along the way
090                    AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
091                    myBuilder.setAgendaId( agendaId );
092                    
093                    if (agendaItemId != null) {
094                            myBuilder = walkAgendaItemTree(agendaItemId, myBuilder);
095                    }
096                    
097                    // build the agenda tree and return it
098                    return myBuilder.build();
099            }
100            
101            @Override
102            public List<AgendaTreeDefinition> getAgendaTrees(List<String> agendaIds) {
103                    List<AgendaTreeDefinition> agendaTrees = new ArrayList<AgendaTreeDefinition>();
104                    for (String agendaId : agendaIds){
105                            agendaTrees.add( getAgendaTree(agendaId) );
106                    }
107            return Collections.unmodifiableList(agendaTrees);               
108            }
109            
110            @Override
111            public RuleDefinition getRule(String ruleId) {
112                    if (StringUtils.isBlank(ruleId)){
113                            return null;                    
114                    }
115                    RuleBo bo = getBusinessObjectService().findBySinglePrimaryKey(RuleBo.class, ruleId);
116                    return RuleBo.to(bo);
117            }
118            
119            @Override
120            public List<RuleDefinition> getRules(List<String> ruleIds) {
121            if (ruleIds == null) throw new IllegalArgumentException("ruleIds must not be null");
122    
123            // Fetch BOs
124            List<RuleBo> bos = null;
125            if (ruleIds.size() == 0) {
126                bos = Collections.emptyList();
127            } else {
128                QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();
129                List<Predicate> pList = new ArrayList<Predicate>();
130                qBuilder.setPredicates(in("id", ruleIds.toArray()));
131                GenericQueryResults<RuleBo> results = getCriteriaLookupService().lookup(RuleBo.class, qBuilder.build());
132    
133                bos = results.getResults();
134            }
135    
136            // Translate BOs
137            ArrayList<RuleDefinition> rules = new ArrayList<RuleDefinition>();
138            for (RuleBo bo : bos) {
139                RuleDefinition rule = RuleBo.to(bo);
140                rules.add(rule);
141            }
142            return Collections.unmodifiableList(rules);
143            }
144    
145            /**
146             * Recursive method to create AgendaTreeDefinition builder
147             *      
148             *  
149             */
150            private AgendaTreeDefinition.Builder walkAgendaItemTree(String agendaItemId, AgendaTreeDefinition.Builder builder){
151                    //TODO: prevent circular, endless looping
152                    if (StringUtils.isBlank(agendaItemId)){
153                            return null;
154                    }
155                    // Get AgendaItemDefinition Business Object from database
156                    // NOTE: if we read agendaItem one at a time from db.   Could consider linking in OJB and getting all at once
157                    AgendaItemBo agendaItemBo = getBusinessObjectService().findBySinglePrimaryKey(AgendaItemBo.class, agendaItemId);
158                    
159                    // If Rule  
160                    // TODO: validate that only either rule or subagenda, not both
161                    if (!StringUtils.isBlank( agendaItemBo.getRuleId() )){
162                            // setup new rule entry builder
163                            AgendaTreeRuleEntry.Builder ruleEntryBuilder = AgendaTreeRuleEntry.Builder
164                                            .create(agendaItemBo.getId(), agendaItemBo.getRuleId());
165                            ruleEntryBuilder.setRuleId( agendaItemBo.getRuleId() );
166                            ruleEntryBuilder.setAgendaItemId( agendaItemBo.getId() );
167                            if (agendaItemBo.getWhenTrueId() != null){
168                                    // Go follow the true branch, creating AgendaTreeDefinintion Builder for the
169                                    // true branch level
170                                    AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
171                                    myBuilder.setAgendaId( agendaItemBo.getAgendaId() );
172                                    ruleEntryBuilder.setIfTrue( walkAgendaItemTree(agendaItemBo.getWhenTrueId(),myBuilder));
173                            }
174                            if (agendaItemBo.getWhenFalseId() != null){
175                                    // Go follow the false branch, creating AgendaTreeDefinintion Builder 
176                                    AgendaTreeDefinition.Builder myBuilder = AgendaTreeDefinition.Builder.create();
177                                    myBuilder.setAgendaId( agendaItemBo.getAgendaId() );
178                                    ruleEntryBuilder.setIfFalse( walkAgendaItemTree(agendaItemBo.getWhenFalseId(), myBuilder));
179                            }
180                            // Build the Rule Entry and add it to the AgendaTreeDefinition builder
181                            builder.addRuleEntry( ruleEntryBuilder.build() );
182                    }
183                    // if SubAgenda and a sub agenda tree entry
184                    if (!StringUtils.isBlank(agendaItemBo.getSubAgendaId())) {
185                            AgendaTreeSubAgendaEntry.Builder subAgendaEntryBuilder = 
186                                    AgendaTreeSubAgendaEntry.Builder.create(agendaItemBo.getId(), agendaItemBo.getSubAgendaId());
187                            builder.addSubAgendaEntry( subAgendaEntryBuilder.build() );
188                            }
189    
190                    // if this agenda item has an "After Id", follow that branch
191                    if (!StringUtils.isBlank( agendaItemBo.getAlwaysId() )){
192                            builder = walkAgendaItemTree( agendaItemBo.getAlwaysId(), builder);
193                            
194                    }
195                    return builder;
196            }
197            
198            /**
199             * 
200             * This method converts a {@link ContextSelectionCriteria} object into a
201             * {@link QueryByCriteria} object with the proper predicates for AttributeBo properties.
202             * 
203             * @param selectionCriteria
204             * @return 
205             */
206            private QueryByCriteria buildQuery( ContextSelectionCriteria selectionCriteria ){
207                    Predicate p;
208                    QueryByCriteria.Builder qBuilder = QueryByCriteria.Builder.create();
209            List<Predicate> pList = new ArrayList<Predicate>();
210            if (selectionCriteria.getNamespaceCode() != null){
211                    p = equal(PropertyNames.Context.NAMESPACE, selectionCriteria.getNamespaceCode());
212                    pList.add(p);
213            }
214            if (selectionCriteria.getName() != null){
215                    p = equal(PropertyNames.Context.NAME, selectionCriteria.getName());
216                    pList.add(p);
217            }
218            if (selectionCriteria.getContextQualifiers() != null){
219                    for (Map.Entry<String, String> entry : selectionCriteria.getContextQualifiers().entrySet()){
220                            p = and(equal(PropertyNames.Context.ATTRIBUTE_BOS
221                                            + "." + PropertyNames.BaseAttribute.ATTRIBUTE_DEFINITION
222                                            + "." + PropertyNames.KrmsAttributeDefinition.NAME, entry.getKey()),
223                                    equal(PropertyNames.Context.ATTRIBUTE_BOS
224                                            + "." + PropertyNames.BaseAttribute.VALUE, entry.getValue()));
225                            pList.add(p);
226                    }
227            }
228            Predicate[] preds = new Predicate[pList.size()];
229            pList.toArray(preds);
230            qBuilder.setPredicates(and(preds)); 
231                    return qBuilder.build();
232            }
233    
234            /**
235         * Sets the businessObjectService property.
236         *
237         * @param businessObjectService The businessObjectService to set.
238         */
239        public void setBusinessObjectService(final BusinessObjectService businessObjectService) {
240            this.businessObjectService = businessObjectService;
241        }
242    
243        protected BusinessObjectService getBusinessObjectService() {
244                    if ( businessObjectService == null ) {
245                // TODO: inject this instead
246                            businessObjectService = KRADServiceLocator.getBusinessObjectService();
247                    }
248                    return businessObjectService;
249            }
250        
251        /**
252         * Sets the criteriaLookupService attribute value.
253         *
254         * @param criteriaLookupService The criteriaLookupService to set.
255         */
256        public void setCriteriaLookupService(final CriteriaLookupService criteriaLookupService) {
257            this.criteriaLookupService = criteriaLookupService;
258        }
259    
260        protected CriteriaLookupService getCriteriaLookupService() {
261            return criteriaLookupService;
262        }
263        
264    }