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