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 */
016package org.kuali.rice.krms.service.impl;
017
018import org.apache.commons.lang.StringUtils;
019import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
020import org.kuali.rice.core.api.util.tree.Tree;
021import org.kuali.rice.krad.uif.UifConstants;
022import org.kuali.rice.krad.uif.component.Component;
023import org.kuali.rice.krad.uif.container.Container;
024import org.kuali.rice.krad.uif.util.ComponentFactory;
025import org.kuali.rice.krad.uif.util.ComponentUtils;
026import org.kuali.rice.krad.uif.view.View;
027import org.kuali.rice.krad.util.BeanPropertyComparator;
028import org.kuali.rice.krad.util.ObjectUtils;
029import org.kuali.rice.krad.web.form.MaintenanceDocumentForm;
030import org.kuali.rice.krms.api.KrmsConstants;
031import org.kuali.rice.krms.api.repository.LogicalOperator;
032import org.kuali.rice.krms.api.repository.RuleManagementService;
033import org.kuali.rice.krms.api.repository.proposition.PropositionType;
034import org.kuali.rice.krms.api.repository.term.TermRepositoryService;
035import org.kuali.rice.krms.api.repository.type.KrmsTypeDefinition;
036import org.kuali.rice.krms.api.repository.type.KrmsTypeRepositoryService;
037import org.kuali.rice.krms.builder.ComponentBuilder;
038import org.kuali.rice.krms.dto.PropositionEditor;
039import org.kuali.rice.krms.dto.PropositionParameterEditor;
040import org.kuali.rice.krms.dto.RuleEditor;
041import org.kuali.rice.krms.dto.RuleManagementWrapper;
042import org.kuali.rice.krms.dto.TermEditor;
043import org.kuali.rice.krms.dto.TermParameterEditor;
044import org.kuali.rice.krms.impl.repository.KrmsRepositoryServiceLocator;
045import org.kuali.rice.krms.service.TemplateRegistry;
046import org.kuali.rice.krms.tree.RuleViewTreeBuilder;
047import org.kuali.rice.krms.tree.node.CompareTreeNode;
048import org.kuali.rice.krms.tree.RuleCompareTreeBuilder;
049import org.kuali.rice.krms.tree.RuleEditTreeBuilder;
050import org.kuali.rice.krms.tree.RulePreviewTreeBuilder;
051import org.kuali.rice.krms.util.KRMSConstants;
052import org.kuali.rice.krms.util.NaturalLanguageHelper;
053import org.kuali.rice.krms.util.PropositionTreeUtil;
054import org.kuali.rice.krms.dto.TemplateInfo;
055import org.kuali.rice.krms.service.RuleViewHelperService;
056import org.kuali.student.common.uif.service.impl.KSViewHelperServiceImpl;
057import org.kuali.student.r1.common.rice.StudentIdentityConstants;
058import org.kuali.student.r2.core.constants.KSKRMSServiceConstants;
059import org.springframework.beans.BeanUtils;
060
061import javax.xml.namespace.QName;
062import java.util.ArrayList;
063import java.util.Arrays;
064import java.util.List;
065import java.util.Map;
066
067/**
068 * Helpers Service for the Rule Pages.
069 *
070 * @author Kuali Student Team
071 */
072public class RuleViewHelperServiceImpl extends KSViewHelperServiceImpl implements RuleViewHelperService {
073
074    private transient RuleManagementService ruleManagementService;
075    private transient KrmsTypeRepositoryService krmsTypeRepositoryService;
076    private transient TermRepositoryService termRepositoryService;
077
078    private RuleCompareTreeBuilder compareTreeBuilder;
079    private RuleEditTreeBuilder editTreeBuilder;
080    private RulePreviewTreeBuilder previewTreeBuilder;
081    private RuleViewTreeBuilder viewTreeBuilder;
082
083    private NaturalLanguageHelper naturalLanguageHelper;
084
085    private static TemplateRegistry templateRegistry;
086
087    protected RuleEditor getRuleEditor(Object model) {
088        if (model instanceof MaintenanceDocumentForm) {
089            MaintenanceDocumentForm maintenanceDocumentForm = (MaintenanceDocumentForm) model;
090            Object dataObject = maintenanceDocumentForm.getDocument().getNewMaintainableObject().getDataObject();
091
092            if (dataObject instanceof RuleEditor) {
093                return (RuleEditor) dataObject;
094            } else if (dataObject instanceof RuleManagementWrapper) {
095                RuleManagementWrapper wrapper = (RuleManagementWrapper) dataObject;
096                return wrapper.getRuleEditor();
097            }
098        }
099        return null;
100    }
101
102    @Override
103    public TemplateInfo getTemplateForType(String type) {
104        return this.getTemplateRegistry().getTemplateForType(type);
105    }
106
107    @Override
108    protected void addCustomContainerComponents(View view, Object model, Container container) {
109        if (KRMSConstants.KRMS_PROPOSITION_DETAILSECTION_ID.equals(container.getId())) {
110            customizePropositionEditSection(view, model, container);
111        }
112    }
113
114    private void customizePropositionEditSection(View view, Object model, Container container) {
115        //Retrieve the current editing proposition if exists.
116        MaintenanceDocumentForm maintenanceDocumentForm = (MaintenanceDocumentForm) model;
117        Object dataObject = maintenanceDocumentForm.getDocument().getNewMaintainableObject().getDataObject();
118
119        RuleEditor ruleEditor = ((RuleManagementWrapper) dataObject).getRuleEditor();
120        PropositionEditor propEditor = PropositionTreeUtil.getProposition(ruleEditor);
121
122        List<Component> components = new ArrayList<Component>();
123        if (propEditor != null) {
124            //Retrieve the name of the xml component to display for the proposition type.
125            TemplateInfo template = this.getTemplateForType(propEditor.getType());
126
127            if (template != null && template.getComponentId() != null) {
128                Component component = ComponentFactory.getNewComponentInstance(template.getComponentId());
129                view.assignComponentIds(component);
130                if(container.getId().equals(maintenanceDocumentForm.getUpdateComponentId())){
131                    String nodePath = view.getDefaultBindingObjectPath() + "." + propEditor.getBindingPath();
132                    ComponentUtils.pushObjectToContext(component, UifConstants.ContextVariableNames.NODE_PATH, nodePath);
133                    ComponentUtils.prefixBindingPathNested(component, propEditor.getBindingPath());
134                }
135
136                //Add Proposition Type FieldGroup to Tree Node
137                components.add(component);
138            }
139
140            if (template != null && template.getConstantComponentId() != null) {
141                Component component = ComponentFactory.getNewComponentInstance(template.getConstantComponentId());
142                view.assignComponentIds(component);
143
144                //Add Proposition Type FieldGroup to Tree Node
145                components.add(component);
146            }
147        }
148
149        //Do not display if there are no components.
150        if (components.size() == 0) {
151            container.getHeader().setRender(false);
152        }
153
154        container.setItems(components);
155    }
156
157    /**
158     * Validate the proposition.
159     *
160     * @param proposition
161     */
162    @Override
163    public void validateProposition(PropositionEditor proposition) {
164
165        // Retrieve the builder for the current proposition type.
166        ComponentBuilder builder = this.getTemplateRegistry().getComponentBuilderForType(proposition.getType());
167        if (builder != null) {
168            // Execute validation
169            builder.validate(proposition);
170        }
171    }
172
173    /**
174     * Clear the description and natural language on proposition editors.
175     *
176     * @param prop
177     * @return
178     */
179    @Override
180    public void resetDescription(PropositionEditor prop) {
181
182        //If proposition type is null, set description and term null
183        if (prop.getType() == null) {
184            prop.setDescription(StringUtils.EMPTY);
185            prop.setTerm(null);
186            prop.getNaturalLanguage().clear();
187            return;
188        }
189
190        //Build the new termParamters with the matching component builder.
191        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(prop.getPropositionTypeCode())) {
192            Map<String, String> termParameters = null;
193            ComponentBuilder builder = this.getTemplateRegistry().getComponentBuilderForType(prop.getType());
194            if (builder != null) {
195                termParameters = builder.buildTermParameters(prop);
196            }
197
198            List<TermParameterEditor> parameters = new ArrayList<TermParameterEditor>();
199            if (termParameters != null) {
200                for (Map.Entry<String, String> entry : termParameters.entrySet()) {
201
202                    TermParameterEditor parameterEditor = null;
203                    if (prop.getTerm().getParameters() != null) {
204                        for (TermParameterEditor parameter : prop.getTerm().getEditorParameters()) {
205
206                            if (entry.getKey().equals(parameter.getName())) {
207                                parameterEditor = parameter;
208                                parameterEditor.setValue(entry.getValue());
209                                break;
210                            }
211                        }
212                    }
213
214                    //Create a new parameter if not exist.
215                    if (parameterEditor == null) {
216                        parameterEditor = new TermParameterEditor();
217                        parameterEditor.setName(entry.getKey());
218                        parameterEditor.setValue(entry.getValue());
219                    }
220                    parameters.add(parameterEditor);
221                }
222            }
223
224            prop.getTerm().setParameters(parameters);
225
226            //Set the term specification if it doesn't exist.
227            if (prop.getTerm().getSpecification() == null) {
228                String termSpecName = this.getTemplateRegistry().getTermSpecNameForType(prop.getType());
229                prop.getTerm().setSpecification(getTermRepositoryService().getTermSpecificationByNameAndNamespace(termSpecName, KSKRMSServiceConstants.NAMESPACE_CODE));
230            }
231
232        } else {
233            prop.setTerm(null);
234        }
235
236        //Refresh the natural language.
237        prop.getNaturalLanguage().clear();
238    }
239
240    public void configurePropositionForType(PropositionEditor proposition) {
241
242        if (proposition != null) {
243
244            if (PropositionType.COMPOUND.getCode().equalsIgnoreCase(proposition.getPropositionTypeCode())) {
245                return;
246            }
247
248            String propositionTypeId = proposition.getTypeId();
249            if ((propositionTypeId == null) || (propositionTypeId.isEmpty())) {
250                proposition.setType(null);
251                return;
252            }
253
254            KrmsTypeDefinition type = KrmsRepositoryServiceLocator.getKrmsTypeRepositoryService().getTypeById(propositionTypeId);
255            if (type != null) {
256                proposition.setType(type.getName());
257            }
258
259            if (proposition.getTerm() == null) {
260                proposition.setTerm(new TermEditor());
261            }
262
263            String termSpecName = this.getTemplateRegistry().getTermSpecNameForType(proposition.getType());
264            proposition.getTerm().setSpecification(getTermRepositoryService().getTermSpecificationByNameAndNamespace(termSpecName, KSKRMSServiceConstants.NAMESPACE_CODE));
265
266        }
267    }
268
269    @Override
270    public void refreshInitTrees(RuleEditor rule) {
271
272        if (rule == null) {
273            return;
274        }
275
276        //Rebuild the trees
277        rule.setEditTree(this.getEditTreeBuilder().buildTree(rule));
278        rule.setPreviewTree(this.getPreviewTreeBuilder().buildTree(rule));
279
280        //Also reset the logic expression. Should only be done after editTree is already built.
281        if (rule.getProposition() != null) {
282            rule.setLogicArea(PropositionTreeUtil.configureLogicExpression(rule.getPropositionEditor()));
283        } else {
284            rule.setLogicArea(StringUtils.EMPTY);
285        }
286
287    }
288
289    /**
290     * Rebuild the tree used for the view only trees.
291     *
292     * @param rule
293     */
294    @Override
295    public void refreshViewTree(RuleEditor rule) {
296
297        if (rule == null) {
298            return;
299        }
300
301        //Rebuild the trees
302        rule.setViewTree(this.getViewTreeBuilder().buildTree(rule));
303
304    }
305
306    @Override
307    public Tree<CompareTreeNode, String> buildCompareTree(RuleEditor original, RuleEditor compare) throws Exception {
308
309        //Build the Tree
310        return this.getCompareTreeBuilder().buildTree(original, compare);
311
312    }
313
314    @Override
315    public Tree<CompareTreeNode, String> buildMultiViewTree(RuleEditor coRuleEditor, RuleEditor cluRuleEditor) throws Exception {
316
317        //Build the Tree
318        return this.getCompareTreeBuilder().buildTree(coRuleEditor, cluRuleEditor);
319
320    }
321
322    /**
323     * Compare all the propositions in a rule tree with a parent rule tree. Returns false if any proposition's type
324     * or term parameters are not the same.
325     * <p/>
326     * Apart from the type and termparameters, all other detail is derived from the typeid and therefore not included in
327     * the comparison.     *
328     *
329     * @param original
330     * @return boolean
331     * @throws Exception
332     */
333    @Override
334    public Boolean compareRules(RuleEditor original) {
335
336        //Do null check on propositions.
337        RuleEditor compareEditor = original.getParent();
338        if ((compareEditor == null) || (compareEditor.getProposition() == null)) {
339            if (original.getProposition() != null) {
340                return false; //if compare is null and original is not, they differ.
341            } else {
342                return true; //both of them are null.
343            }
344        } else if (original.getProposition() == null) {
345            return false;
346        }
347
348        //Compare Root Proposition Type and if the same test recursively
349        if (original.getProposition().getTypeId().equals(compareEditor.getProposition().getTypeId())) {
350            return compareProposition(original.getPropositionEditor(), compareEditor.getPropositionEditor());
351        } else {
352            return false;
353        }
354    }
355
356    /**
357     * Method to handle the proposition comparison recursively.
358     *
359     * @param original
360     * @param compare
361     * @return true if proposition are the same.
362     */
363    @Override
364    public Boolean compareProposition(PropositionEditor original, PropositionEditor compare) {
365        //Compare the proposition
366        BeanPropertyComparator propositionComparator = new BeanPropertyComparator(Arrays.asList("typeId"));
367        if (propositionComparator.compare(original, compare) != 0) {
368            return false;
369        }
370
371        //Compare the term values
372        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(original.getPropositionTypeCode())) {
373            TermEditor term = new TermEditor(PropositionTreeUtil.getTermParameter(compare.getParameters()).getTermValue());
374            if (!compareTerm(original.getTerm().getEditorParameters(), term.getEditorParameters())) {
375                return false;
376            }
377        }
378
379        //Compare the compound propositions.
380        return compareCompoundProposition(original.getCompoundEditors(), compare.getCompoundEditors());
381    }
382
383    /**
384     * Compare all the keys and values of the term parameter. Returns false if any of the keys (names) or
385     * values of the term paramters is not the same.
386     *
387     * @param original list of term parameters for current term
388     * @param compare  list of term paramters to compare with.
389     * @return true if all names and values are the same.
390     */
391    @Override
392    public Boolean compareTerm(List<TermParameterEditor> original, List<TermParameterEditor> compare) {
393
394        //If the sizes doesn't match, they are not same.
395        int originalSize = original == null ? 0 : original.size();
396        if (originalSize != (compare == null ? 0 : compare.size())) {
397            return false;
398        } else if (originalSize > 0) {
399
400            //Compare the compound propositions.
401            BeanPropertyComparator termComparator = new BeanPropertyComparator(Arrays.asList("name"));
402            for (int index = 0; index < originalSize; index++) {
403                if (termComparator.compare(original.get(index), compare.get(index)) != 0) {
404                    return false;
405                }
406            }
407        }
408
409        return true;
410
411    }
412
413    /**
414     * Recursively compare child propositions.
415     *
416     * @param original
417     * @param compare
418     * @return
419     */
420    @Override
421    public Boolean compareCompoundProposition(List<PropositionEditor> original, List<PropositionEditor> compare) {
422
423        //If the sizes doesn't match, they are not same.
424        int originalSize = original == null ? 0 : original.size();
425        if (originalSize != (compare == null ? 0 : compare.size())) {
426            return false;
427        } else if (originalSize > 0) {
428
429            //Compare the compound propositions.
430            for (int index = 0; index < originalSize; index++) {
431                if (!compareProposition(original.get(index), compare.get(index))) {
432                    return false;
433                }
434            }
435        }
436
437        return true;
438    }
439
440    /**
441     * Make a new copy of the current proposition including the compounds.
442     * <p/>
443     * The deepcopy is done to make sure that create a full copy and does not only copy the references.
444     *
445     * @param oldProposition
446     * @return
447     */
448    @Override
449    public PropositionEditor copyProposition(PropositionEditor oldProposition) {
450        try {
451            PropositionEditor newProposition = this.copyPropositionEditor(oldProposition);
452
453            //Use a deepcopy to create new references to inner objects such as string.
454            return (PropositionEditor) ObjectUtils.deepCopy(newProposition);
455        } catch (Exception e) {
456            return null;
457        }
458    }
459
460    /**
461     * Used when the user clicked the copy button. It creates a new copy of the proposition with all the related
462     * compound propositions.
463     * <p/>
464     * The compound propositions is handled recursively.
465     *
466     * @param oldProposition
467     * @return
468     */
469    protected PropositionEditor copyPropositionEditor(PropositionEditor oldProposition) {
470        PropositionEditor newProposition;
471        try {
472            newProposition = this.getPropositionEditorClass().newInstance();
473        } catch (Exception e) {
474            newProposition = new PropositionEditor();
475        }
476        BeanUtils.copyProperties(oldProposition, newProposition, new String[]{"key", "id", "term", "parameters"});
477
478        if (!oldProposition.getPropositionTypeCode().equals("C")) {
479            List<PropositionParameterEditor> propositionParameterEditors = new ArrayList<PropositionParameterEditor>();
480            for (PropositionParameterEditor parm : oldProposition.getParameters()) {
481                PropositionParameterEditor newParm = new PropositionParameterEditor();
482                BeanUtils.copyProperties(parm, newParm, new String[]{"termValue", "id", "versionNumber"});
483                propositionParameterEditors.add(newParm);
484            }
485
486            newProposition.setParameters(propositionParameterEditors);
487
488            TermEditor termEditor = new TermEditor();
489            List<TermParameterEditor> termParameterEditors = new ArrayList<TermParameterEditor>();
490           if( oldProposition.getTerm() != null) {
491            BeanUtils.copyProperties(oldProposition.getTerm(), termEditor, new String[]{"id", "versionNumber", "parameters"});
492            for (TermParameterEditor termParm : oldProposition.getTerm().getEditorParameters()) {
493                TermParameterEditor newTermParm = new TermParameterEditor();
494                BeanUtils.copyProperties(termParm, newTermParm, new String[]{"id", "versionNumber"});
495                termParameterEditors.add(newTermParm);
496            }
497           }
498            termEditor.setParameters(termParameterEditors);
499
500            newProposition.setTerm(termEditor);
501            this.resetDescription(newProposition);
502        }
503
504        if (newProposition.getCompoundEditors() != null) {
505            List<PropositionEditor> props = new ArrayList<PropositionEditor>();
506            for (PropositionEditor prop : newProposition.getCompoundEditors()) {
507                props.add(this.copyPropositionEditor(prop));
508            }
509            newProposition.setCompoundEditors(props);
510        }
511
512
513        return newProposition;
514    }
515
516    @Override
517    public PropositionEditor createCompoundPropositionBoStub(PropositionEditor existing, boolean addNewChild) {
518        try {
519            PropositionEditor compound = PropositionTreeUtil.createCompoundPropositionBoStub(existing, addNewChild, this.getPropositionEditorClass());
520            this.setTypeForCompoundOpCode(compound, LogicalOperator.AND.getCode());
521            return compound;
522        } catch (Exception e) {
523            return null;
524        }
525    }
526
527    @Override
528    public void setTypeForCompoundOpCode(PropositionEditor proposition, String compoundOpCode) {
529        //Return as quickly as possible for performance.
530        if (compoundOpCode.equals(proposition.getCompoundOpCode())) {
531            return;
532        }
533
534        //Clear the natural language so the the tree builder can rebuild it.
535        proposition.getNaturalLanguage().clear();
536        proposition.setCompoundOpCode(compoundOpCode);
537    }
538
539    /**
540     * Creates a new instance of a simple proposition.
541     *
542     * @param sibling
543     * @return
544     */
545    @Override
546    public PropositionEditor createSimplePropositionBoStub(PropositionEditor sibling) {
547        try {
548            return PropositionTreeUtil.createSimplePropositionBoStub(sibling, this.getPropositionEditorClass());
549        } catch (Exception e) {
550            return null;
551        }
552    }
553
554    /**
555     * Override this method to return a different class type if you need to use a different propositoin editor class.
556     *
557     * @return
558     */
559    public Class<? extends PropositionEditor> getPropositionEditorClass() {
560        return PropositionEditor.class;
561    }
562
563    protected RuleManagementService getRuleManagementService() {
564        if (ruleManagementService == null) {
565            ruleManagementService = (RuleManagementService) GlobalResourceLoader.getService(QName.valueOf("ruleManagementService"));
566        }
567        return ruleManagementService;
568    }
569
570    protected RuleCompareTreeBuilder getCompareTreeBuilder() {
571        if (compareTreeBuilder == null) {
572            compareTreeBuilder = new RuleCompareTreeBuilder();
573        }
574        return compareTreeBuilder;
575    }
576
577    protected RuleEditTreeBuilder getEditTreeBuilder() {
578        if (editTreeBuilder == null) {
579            editTreeBuilder = new RuleEditTreeBuilder();
580        }
581        return editTreeBuilder;
582    }
583
584    protected RulePreviewTreeBuilder getPreviewTreeBuilder() {
585        if (previewTreeBuilder == null) {
586            previewTreeBuilder = new RulePreviewTreeBuilder();
587        }
588        return previewTreeBuilder;
589    }
590
591    protected RuleViewTreeBuilder getViewTreeBuilder() {
592        if (viewTreeBuilder == null) {
593            viewTreeBuilder = new RuleViewTreeBuilder();
594        }
595        return viewTreeBuilder;
596    }
597
598    protected NaturalLanguageHelper getNaturalLanguageHelper() {
599        if (naturalLanguageHelper == null) {
600            naturalLanguageHelper = new NaturalLanguageHelper();
601            naturalLanguageHelper.setRuleManagementService(this.getRuleManagementService());
602        }
603        return naturalLanguageHelper;
604    }
605
606    protected TemplateRegistry getTemplateRegistry() {
607        if (templateRegistry == null) {
608            templateRegistry = (TemplateRegistry) GlobalResourceLoader.getService(QName.valueOf("templateResolverMockService"));
609        }
610        return templateRegistry;
611    }
612
613    protected KrmsTypeRepositoryService getKrmsTypeRepositoryService() {
614        if (krmsTypeRepositoryService == null) {
615            krmsTypeRepositoryService = (KrmsTypeRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "krmsTypeRepositoryService"));
616        }
617        return krmsTypeRepositoryService;
618    }
619
620    public TermRepositoryService getTermRepositoryService() {
621        if (termRepositoryService == null) {
622            termRepositoryService = (TermRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "termRepositoryService"));
623        }
624        return termRepositoryService;
625    }
626
627}