001/**
002 * Copyright 2005-2013 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/*
017 * To change this template, choose Tools | Templates
018 * and open the template in the editor.
019 */
020package org.kuali.rice.krms.impl.repository;
021
022import java.util.ArrayList;
023import java.util.HashMap;
024import java.util.LinkedHashMap;
025import java.util.List;
026import java.util.Map;
027
028import org.apache.commons.lang.StringUtils;
029import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
030import org.kuali.rice.krms.api.repository.LogicalOperator;
031import org.kuali.rice.krms.api.repository.NaturalLanguageTree;
032import org.kuali.rice.krms.api.repository.RuleManagementService;
033import org.kuali.rice.krms.api.repository.TranslateBusinessMethods;
034import org.kuali.rice.krms.api.repository.agenda.AgendaDefinition;
035import org.kuali.rice.krms.api.repository.agenda.AgendaItemDefinition;
036import org.kuali.rice.krms.api.repository.language.NaturalLanguageTemplate;
037import org.kuali.rice.krms.api.repository.language.NaturalLanguageTemplaterContract;
038import org.kuali.rice.krms.api.repository.proposition.PropositionDefinition;
039import org.kuali.rice.krms.api.repository.proposition.PropositionParameter;
040import org.kuali.rice.krms.api.repository.proposition.PropositionParameterType;
041import org.kuali.rice.krms.api.repository.proposition.PropositionType;
042import org.kuali.rice.krms.api.repository.rule.RuleDefinition;
043import org.kuali.rice.krms.api.repository.term.TermDefinition;
044import org.kuali.rice.krms.api.repository.term.TermParameterDefinition;
045import org.kuali.rice.krms.api.repository.term.TermRepositoryService;
046
047/**
048 * @author nwright
049 */
050public class KSTranslationUtility implements TranslateBusinessMethods {
051
052    private RuleManagementService ruleManagementService;
053    private TermRepositoryService termRepositoryService;
054    private NaturalLanguageTemplaterContract templater;
055
056    public KSTranslationUtility(RuleManagementService ruleManagementService, TermRepositoryService termRepositoryService,
057                              NaturalLanguageTemplaterContract templater) {
058        this.ruleManagementService = ruleManagementService;
059        this.termRepositoryService = termRepositoryService;
060        this.templater = templater;
061    }
062
063    public RuleManagementService getRuleManagementService() {
064        return ruleManagementService;
065    }
066
067    public void setRuleManagementService(RuleManagementService ruleManagementService) {
068        this.ruleManagementService = ruleManagementService;
069    }
070
071    public NaturalLanguageTemplaterContract getTemplater() {
072        return templater;
073    }
074
075    public void setTemplater(NaturalLanguageTemplaterContract templater) {
076        this.templater = templater;
077    }
078
079    @Override
080    public String translateNaturalLanguageForObject(String naturalLanguageUsageId, String typeId, String krmsObjectId, String languageCode)
081            throws RiceIllegalArgumentException {
082
083        Map<String, NaturalLanguageTemplate> templateMap = getNaturalLanguageTemplateMap(naturalLanguageUsageId, languageCode);
084
085        if (typeId.equals("agenda")) {
086            AgendaDefinition agenda = this.ruleManagementService.getAgenda(krmsObjectId);
087            if (agenda == null) {
088                throw new RiceIllegalArgumentException(krmsObjectId + " is not an Id for an agenda");
089            }
090            return this.translateNaturalLanguageForAgenda(agenda, templateMap);
091        } else if (typeId.equals("rule")) {
092            RuleDefinition rule = this.ruleManagementService.getRule(krmsObjectId);
093            if (rule == null) {
094                throw new RiceIllegalArgumentException(krmsObjectId + " is not an Id for a rule");
095            }
096            return this.translateNaturalLanguageForRule(rule, templateMap);
097        } else if (typeId.equals("proposition")) {
098            PropositionDefinition proposition = this.ruleManagementService.getProposition(krmsObjectId);
099            if (proposition == null) {
100                throw new RiceIllegalArgumentException(krmsObjectId + " is not an Id for a proposition");
101            }
102            return this.translateNaturalLanguageForProposition(naturalLanguageUsageId, proposition, languageCode);
103        }
104
105        return StringUtils.EMPTY;
106    }
107
108    protected String translateNaturalLanguageForAgenda(AgendaDefinition agenda, Map<String, NaturalLanguageTemplate> templateMap) throws RiceIllegalArgumentException {
109        if (agenda.getFirstItemId() == null) {
110            throw new RiceIllegalArgumentException("Agenda has no first item");
111        }
112
113        AgendaItemDefinition item = this.ruleManagementService.getAgendaItem(agenda.getFirstItemId());
114        return translateNaturalLanguageForAgendaItem(item, templateMap);
115    }
116
117    protected String translateNaturalLanguageForAgendaItem(AgendaItemDefinition item, Map<String, NaturalLanguageTemplate> templateMap) {
118        if(item==null){
119            return StringUtils.EMPTY;
120        }
121
122        String naturalLanguage = StringUtils.EMPTY;
123        if (item.getRuleId() != null) {
124            RuleDefinition rule = this.ruleManagementService.getRule(item.getRuleId());
125            naturalLanguage += this.translateNaturalLanguageForRule(rule, templateMap);
126        }
127        naturalLanguage += translateNaturalLanguageForAgendaItem(item.getWhenTrue(), templateMap);
128        naturalLanguage += translateNaturalLanguageForAgendaItem(item.getWhenFalse(), templateMap);
129        naturalLanguage += translateNaturalLanguageForAgendaItem(item.getAlways(), templateMap);
130        return naturalLanguage;
131    }
132
133    protected String translateNaturalLanguageForRule(RuleDefinition rule, Map<String, NaturalLanguageTemplate> templateMap) throws RiceIllegalArgumentException {
134        if(rule==null){
135            return StringUtils.EMPTY;
136        }
137
138        NaturalLanguageTemplate nlTemplate = templateMap.get(rule.getTypeId());
139        String naturalLanguage = nlTemplate.getTemplate() + " ";
140
141        if(rule.getProposition()!=null){
142            naturalLanguage += this.translateNaturalLanguageForProposition(rule.getProposition(), templateMap, true) + ". ";
143        }
144
145        return naturalLanguage;
146    }
147
148    @Override
149    public String translateNaturalLanguageForProposition(String naturalLanguageUsageId,
150                                                         PropositionDefinition proposition, String languageCode)
151            throws RiceIllegalArgumentException {
152
153        Map<String, NaturalLanguageTemplate> templateMap = getNaturalLanguageTemplateMap(naturalLanguageUsageId, languageCode);
154        return translateNaturalLanguageForProposition(proposition, templateMap, true) + ". ";
155    }
156
157    private Map<String, NaturalLanguageTemplate> getNaturalLanguageTemplateMap(String naturalLanguageUsageId, String languageCode) {
158        Map<String, NaturalLanguageTemplate> templateMap = new HashMap<String, NaturalLanguageTemplate>();
159        List<NaturalLanguageTemplate> templates = this.ruleManagementService.findNaturalLanguageTemplatesByNaturalLanguageUsage(naturalLanguageUsageId);
160        for(NaturalLanguageTemplate nlTemplate : templates){
161            if(languageCode.equals(nlTemplate.getLanguageCode())){
162                templateMap.put(nlTemplate.getTypeId(), nlTemplate);
163            }
164        }
165        return templateMap;
166    }
167
168    /**
169     * These constants declared here for Rice if they would like to move them
170     * to a constants class of their choice. Need to be able to add a
171     * blank template in the DB. Rice validations counter it.
172     */
173     private final String KRMS_NL_TEMP_BLANK = "kuali.krms.nl.template.blank";
174     private final String KRMS_NL_TEMP_ATTR_OPERATOR = "kuali.krms.nl.template.attribute.operator";
175
176    /**
177     * This method is added because from a functional point of view the root proposition is ignored when it is a group
178     * and therefore handled differently.
179     *
180     * @param proposition
181     * @param templateMap
182     * @param isRoot
183     * @return
184     */
185    private String translateNaturalLanguageForProposition(PropositionDefinition proposition, Map<String, NaturalLanguageTemplate> templateMap, boolean isRoot) {
186        NaturalLanguageTemplate naturalLanguageTemplate = templateMap.get(proposition.getTypeId());
187
188        StringBuilder naturalLanguage = new StringBuilder();
189        if (proposition.getPropositionTypeCode().equals(PropositionType.SIMPLE.getCode())) {
190            if(naturalLanguageTemplate!=null){
191                Map<String, Object> contextMap = this.buildSimplePropositionContextMap(proposition);
192                naturalLanguage.append(templater.translate(naturalLanguageTemplate, contextMap));
193            }
194
195        } else if (proposition.getPropositionTypeCode().equals(PropositionType.COMPOUND.getCode())) {
196
197            if(naturalLanguageTemplate!=null && !naturalLanguageTemplate.getTemplate().equals(KRMS_NL_TEMP_BLANK)){
198                Map<String, Object> contextMap = this.buildCompoundPropositionContextMap(proposition, templateMap);
199                naturalLanguage.append(templater.translate(naturalLanguageTemplate, contextMap));
200            }
201
202            //Null check because newly created compound propositions should also be translateable.
203            if(proposition.getCompoundComponents()!=null){
204                /*
205                  Take note when working in this part of the method AND only for Final Matrix Exam. The same idea is carried out in
206                  FERuleViewHelperServiceImpl.getDescriptionForPropositionTree
207                  ln: 218 but that method is mainly used with data that is in memory whereas this method makes DB call.
208                 */
209                String operator = getCompoundSeperator(naturalLanguageTemplate, isRoot);
210
211                for (PropositionDefinition child : proposition.getCompoundComponents()) {
212                    if(proposition.getCompoundComponents().indexOf(child)!=0){
213                        naturalLanguage.append(operator);
214                    }
215                    naturalLanguage.append(this.translateNaturalLanguageForProposition(child, templateMap, false));
216                }
217            }
218
219        } else {
220            throw new RiceIllegalArgumentException("Unknown proposition type: " + proposition.getPropositionTypeCode());
221        }
222
223        return naturalLanguage.toString();
224    }
225
226    private String getCompoundSeperator(NaturalLanguageTemplate naturalLanguageTemplate, boolean isRoot) {
227        String operator = naturalLanguageTemplate.getAttributes().get(KRMS_NL_TEMP_ATTR_OPERATOR);
228        if (isRoot){
229            return ". " + operator + " ";
230        }
231        return "; " + operator + " ";
232    }
233
234
235    @Override
236    public NaturalLanguageTree translateNaturalLanguageTreeForProposition(String naturalLanguageUsageId,
237                                                                          PropositionDefinition proposition,
238                                                                          String languageCode) throws RiceIllegalArgumentException {
239
240        Map<String, NaturalLanguageTemplate> templateMap = getNaturalLanguageTemplateMap(naturalLanguageUsageId, languageCode);
241        return translateNaturalLanguageTreeForProposition(proposition, templateMap);
242    }
243
244    public NaturalLanguageTree translateNaturalLanguageTreeForProposition(PropositionDefinition proposition,
245                                                                          Map<String, NaturalLanguageTemplate> templateMap) throws RiceIllegalArgumentException {
246
247        NaturalLanguageTemplate naturalLanguageTemplate = templateMap.get(proposition.getTypeId());
248
249        NaturalLanguageTree.Builder tree = NaturalLanguageTree.Builder.create();
250        if (proposition.getPropositionTypeCode().equals(PropositionType.SIMPLE.getCode())) {
251            Map<String, Object> contextMap = this.buildSimplePropositionContextMap(proposition);
252            String naturalLanguage = templater.translate(naturalLanguageTemplate, contextMap);
253            tree.setNaturalLanguage(naturalLanguage);
254
255        } else if (proposition.getPropositionTypeCode().equals(PropositionType.COMPOUND.getCode())) {
256            Map<String, Object> contextMap = this.buildCompoundPropositionContextMap(proposition, templateMap);
257            String naturalLanguage = templater.translate(naturalLanguageTemplate, contextMap);
258            tree.setNaturalLanguage(naturalLanguage);
259
260            //Null check because newly created compound propositions should also be translateable.
261            if(proposition.getCompoundComponents()!=null){
262                List<NaturalLanguageTree> children = new ArrayList<NaturalLanguageTree>();
263                for (PropositionDefinition child : proposition.getCompoundComponents()) {
264                    children.add(this.translateNaturalLanguageTreeForProposition(child, templateMap));
265                }
266                tree.setChildren(children);
267            }
268
269        } else {
270            throw new RiceIllegalArgumentException("Unknown proposition type: " + proposition.getPropositionTypeCode());
271        }
272
273        return tree.build();
274    }
275
276    protected Map<String, Object> buildSimplePropositionContextMap(PropositionDefinition proposition) {
277        if (!proposition.getPropositionTypeCode().equals(PropositionType.SIMPLE.getCode())) {
278            throw new RiceIllegalArgumentException("proposition is not simple " + proposition.getPropositionTypeCode() + " " + proposition.getId() + proposition.getDescription());
279        }
280        Map<String, Object> contextMap = new LinkedHashMap<String, Object>();
281        for (PropositionParameter param : proposition.getParameters()) {
282            if (param.getParameterType().equals(PropositionParameterType.TERM.getCode())) {
283                TermDefinition term = param.getTermValue();
284                if ((term == null) && (StringUtils.isNotBlank(param.getValue()))) {
285                    term = this.termRepositoryService.getTerm(param.getValue());
286                }
287                if (term != null) {
288                    for (TermParameterDefinition termParam : term.getParameters()) {
289                        contextMap.put(termParam.getName(), termParam.getValue());
290                    }
291                } else {
292                    contextMap.put(param.getParameterType(), param.getValue());
293                }
294            } else {
295                contextMap.put(param.getParameterType(), param.getValue());
296            }
297        }
298        return contextMap;
299    }
300
301    protected Map<String, Object> buildCompoundPropositionContextMap(PropositionDefinition proposition, Map<String, NaturalLanguageTemplate> templateMap) {
302        if (!proposition.getPropositionTypeCode().equals(PropositionType.COMPOUND.getCode())) {
303            throw new RiceIllegalArgumentException("proposition us not compound " + proposition.getPropositionTypeCode() + " " + proposition.getId() + proposition.getDescription());
304        }
305        return new LinkedHashMap<String, Object>();
306    }
307
308}