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