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
281    /**
282     * Rebuild the tree used for the view only trees.
283     *
284     * @param rule
285     */
286    @Override
287    public void refreshViewTree(RuleEditor rule) {
288
289        if (rule == null) {
290            return;
291        }
292
293        //Rebuild the trees
294        rule.setViewTree(this.getViewTreeBuilder().buildTree(rule));
295
296    }
297
298    @Override
299    public Tree<CompareTreeNode, String> buildCompareTree(RuleEditor original, RuleEditor compare) {
300
301        //Build the Tree
302        return this.getCompareTreeBuilder().buildTree(original, compare);
303
304    }
305
306    @Override
307    public Tree<CompareTreeNode, String> buildMultiViewTree(RuleEditor coRuleEditor, RuleEditor cluRuleEditor) {
308
309        //Build the Tree
310        return this.getCompareTreeBuilder().buildTree(coRuleEditor, cluRuleEditor);
311
312    }
313
314    /**
315     * Compare all the propositions in a rule tree with a parent rule tree. Returns false if any proposition's type
316     * or term parameters are not the same.
317     * <p/>
318     * Apart from the type and termparameters, all other detail is derived from the typeid and therefore not included in
319     * the comparison.     *
320     *
321     * @param original
322     * @return boolean
323     * @throws Exception
324     */
325    @Override
326    public Boolean compareRules(RuleEditor original) {
327
328        //Do null check on propositions.
329        RuleEditor compareEditor = original.getParent();
330        if ((compareEditor == null) || (compareEditor.getProposition() == null)) {
331            if (original.getProposition() != null) {
332                return false; //if compare is null and original is not, they differ.
333            } else {
334                return true; //both of them are null.
335            }
336        } else if (original.getProposition() == null) {
337            return false;
338        }
339
340        //Compare Root Proposition Type and if the same test recursively
341        if (original.getProposition().getTypeId().equals(compareEditor.getProposition().getTypeId())) {
342            return compareProposition(original.getPropositionEditor(), compareEditor.getPropositionEditor());
343        } else {
344            return false;
345        }
346    }
347
348    /**
349     * Method to handle the proposition comparison recursively.
350     *
351     * @param original
352     * @param compare
353     * @return true if proposition are the same.
354     */
355    @Override
356    public Boolean compareProposition(PropositionEditor original, PropositionEditor compare) {
357        //Compare the proposition
358        BeanPropertyComparator propositionComparator = new BeanPropertyComparator(Arrays.asList("typeId"));
359        if (propositionComparator.compare(original, compare) != 0) {
360            return false;
361        }
362
363        //Compare the term values
364        if (PropositionType.SIMPLE.getCode().equalsIgnoreCase(original.getPropositionTypeCode())) {
365            TermEditor term = new TermEditor(PropositionTreeUtil.getTermParameter(compare.getParameters()).getTermValue());
366            if (!compareTerm(original.getTerm().getEditorParameters(), term.getEditorParameters())) {
367                return false;
368            }
369        }
370
371        //Compare the compound propositions.
372        return compareCompoundProposition(original.getCompoundEditors(), compare.getCompoundEditors());
373    }
374
375    /**
376     * Compare all the keys and values of the term parameter. Returns false if any of the keys (names) or
377     * values of the term paramters is not the same.
378     *
379     * @param original list of term parameters for current term
380     * @param compare  list of term paramters to compare with.
381     * @return true if all names and values are the same.
382     */
383    @Override
384    public Boolean compareTerm(List<TermParameterEditor> original, List<TermParameterEditor> compare) {
385
386        //If the sizes doesn't match, they are not same.
387        int originalSize = original == null ? 0 : original.size();
388        if (originalSize != (compare == null ? 0 : compare.size())) {
389            return false;
390        } else if (originalSize > 0) {
391
392            //Compare the compound propositions.
393            BeanPropertyComparator termComparator = new BeanPropertyComparator(Arrays.asList("name","value"));
394            for (int index = 0; index < originalSize; index++) {
395                if (termComparator.compare(original.get(index), compare.get(index)) != 0) {
396                    return false;
397                }
398            }
399        }
400
401        return true;
402
403    }
404
405    /**
406     * Recursively compare child propositions.
407     *
408     * @param original
409     * @param compare
410     * @return
411     */
412    @Override
413    public Boolean compareCompoundProposition(List<PropositionEditor> original, List<PropositionEditor> compare) {
414
415        //If the sizes doesn't match, they are not same.
416        int originalSize = original == null ? 0 : original.size();
417        if (originalSize != (compare == null ? 0 : compare.size())) {
418            return false;
419        } else if (originalSize > 0) {
420
421            //Compare the compound propositions.
422            for (int index = 0; index < originalSize; index++) {
423                if (!compareProposition(original.get(index), compare.get(index))) {
424                    return false;
425                }
426            }
427        }
428
429        return true;
430    }
431
432    /**
433     * Make a new copy of the current proposition including the compounds.
434     * <p/>
435     * The deepcopy is done to make sure that create a full copy and does not only copy the references.
436     *
437     * @param oldProposition
438     * @return
439     */
440    @Override
441    public PropositionEditor copyProposition(PropositionEditor oldProposition) {
442        try {
443            PropositionEditor newProposition = this.copyPropositionEditor(oldProposition);
444
445            //Use a deepcopy to create new references to inner objects such as string.
446            return (PropositionEditor) ObjectUtils.deepCopy(newProposition);
447        } catch (Exception e) {
448            return null;
449        }
450    }
451
452    /**
453     * Used when the user clicked the copy button. It creates a new copy of the proposition with all the related
454     * compound propositions.
455     * <p/>
456     * The compound propositions is handled recursively.
457     *
458     * @param oldProposition
459     * @return
460     */
461    protected PropositionEditor copyPropositionEditor(PropositionEditor oldProposition) {
462        PropositionEditor newProposition;
463        try {
464            newProposition = this.getPropositionEditorClass().newInstance();
465        } catch (Exception e) {
466            newProposition = new PropositionEditor();
467        }
468        BeanUtils.copyProperties(oldProposition, newProposition, new String[]{"key", "id", "term", "parameters"});
469
470        if (!oldProposition.getPropositionTypeCode().equals("C")) {
471            List<PropositionParameterEditor> propositionParameterEditors = new ArrayList<PropositionParameterEditor>();
472            for (PropositionParameterEditor parm : oldProposition.getParameters()) {
473                PropositionParameterEditor newParm = new PropositionParameterEditor();
474                BeanUtils.copyProperties(parm, newParm, new String[]{"termValue", "id", "versionNumber"});
475                propositionParameterEditors.add(newParm);
476            }
477
478            newProposition.setParameters(propositionParameterEditors);
479
480            TermEditor termEditor = new TermEditor();
481            List<TermParameterEditor> termParameterEditors = new ArrayList<TermParameterEditor>();
482           if( oldProposition.getTerm() != null) {
483            BeanUtils.copyProperties(oldProposition.getTerm(), termEditor, new String[]{"id", "versionNumber", "parameters"});
484            for (TermParameterEditor termParm : oldProposition.getTerm().getEditorParameters()) {
485                TermParameterEditor newTermParm = new TermParameterEditor();
486                BeanUtils.copyProperties(termParm, newTermParm, new String[]{"id", "versionNumber"});
487                termParameterEditors.add(newTermParm);
488            }
489           }
490            termEditor.setParameters(termParameterEditors);
491
492            newProposition.setTerm(termEditor);
493            this.resetDescription(newProposition);
494        }
495
496        if (newProposition.getCompoundEditors() != null) {
497            List<PropositionEditor> props = new ArrayList<PropositionEditor>();
498            for (PropositionEditor prop : newProposition.getCompoundEditors()) {
499                props.add(this.copyPropositionEditor(prop));
500            }
501            newProposition.setCompoundEditors(props);
502        }
503
504
505        return newProposition;
506    }
507
508    @Override
509    public PropositionEditor createCompoundPropositionBoStub(PropositionEditor existing, boolean addNewChild) {
510        try {
511            PropositionEditor compound = PropositionTreeUtil.createCompoundPropositionBoStub(existing, addNewChild, this.getPropositionEditorClass());
512            this.setTypeForCompoundOpCode(compound, LogicalOperator.AND.getCode());
513            return compound;
514        } catch (Exception e) {
515            return null;
516        }
517    }
518
519    @Override
520    public void setTypeForCompoundOpCode(PropositionEditor proposition, String compoundOpCode) {
521        //Return as quickly as possible for performance.
522        if (compoundOpCode.equals(proposition.getCompoundOpCode())) {
523            return;
524        }
525
526        //Clear the natural language so the the tree builder can rebuild it.
527        proposition.getNaturalLanguage().clear();
528        proposition.setCompoundOpCode(compoundOpCode);
529    }
530
531    /**
532     * Creates a new instance of a simple proposition.
533     *
534     * @param sibling
535     * @return
536     */
537    @Override
538    public PropositionEditor createSimplePropositionBoStub(PropositionEditor sibling) {
539        try {
540            return PropositionTreeUtil.createSimplePropositionBoStub(sibling, this.getPropositionEditorClass());
541        } catch (Exception e) {
542            return null;
543        }
544    }
545
546    /**
547     * Override this method to return a different class type if you need to use a different propositoin editor class.
548     *
549     * @return
550     */
551    public Class<? extends PropositionEditor> getPropositionEditorClass() {
552        return PropositionEditor.class;
553    }
554
555    protected RuleManagementService getRuleManagementService() {
556        if (ruleManagementService == null) {
557            ruleManagementService = (RuleManagementService) GlobalResourceLoader.getService(QName.valueOf("ruleManagementService"));
558        }
559        return ruleManagementService;
560    }
561
562    protected RuleCompareTreeBuilder getCompareTreeBuilder() {
563        if (compareTreeBuilder == null) {
564            compareTreeBuilder = new RuleCompareTreeBuilder();
565        }
566        return compareTreeBuilder;
567    }
568
569    protected RuleEditTreeBuilder getEditTreeBuilder() {
570        if (editTreeBuilder == null) {
571            editTreeBuilder = new RuleEditTreeBuilder();
572        }
573        return editTreeBuilder;
574    }
575
576    protected RulePreviewTreeBuilder getPreviewTreeBuilder() {
577        if (previewTreeBuilder == null) {
578            previewTreeBuilder = new RulePreviewTreeBuilder();
579        }
580        return previewTreeBuilder;
581    }
582
583    protected RuleViewTreeBuilder getViewTreeBuilder() {
584        if (viewTreeBuilder == null) {
585            viewTreeBuilder = new RuleViewTreeBuilder();
586        }
587        return viewTreeBuilder;
588    }
589
590    protected NaturalLanguageHelper getNaturalLanguageHelper() {
591        if (naturalLanguageHelper == null) {
592            naturalLanguageHelper = new NaturalLanguageHelper();
593            naturalLanguageHelper.setRuleManagementService(this.getRuleManagementService());
594        }
595        return naturalLanguageHelper;
596    }
597
598    protected TemplateRegistry getTemplateRegistry() {
599        if (templateRegistry == null) {
600            templateRegistry = (TemplateRegistry) GlobalResourceLoader.getService(new QName("http://student.kuali.org/wsdl/templateResolverService", "templateResolverService"));
601        }
602        return templateRegistry;
603    }
604
605    protected KrmsTypeRepositoryService getKrmsTypeRepositoryService() {
606        if (krmsTypeRepositoryService == null) {
607            krmsTypeRepositoryService = (KrmsTypeRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "krmsTypeRepositoryService"));
608        }
609        return krmsTypeRepositoryService;
610    }
611
612    public TermRepositoryService getTermRepositoryService() {
613        if (termRepositoryService == null) {
614            termRepositoryService = (TermRepositoryService) GlobalResourceLoader.getService(new QName(KrmsConstants.Namespaces.KRMS_NAMESPACE_2_0, "termRepositoryService"));
615        }
616        return termRepositoryService;
617    }
618
619}