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