/* * Copyright 2012 Red Hat, Inc. and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.drools.workbench.screens.guided.rule.client.editor; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.event.logical.shared.ValueChangeEvent; import com.google.gwt.event.logical.shared.ValueChangeHandler; import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.Command; import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlexTable; import com.google.gwt.user.client.ui.HTML; import com.google.gwt.user.client.ui.HasHorizontalAlignment; import com.google.gwt.user.client.ui.HasVerticalAlignment; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Widget; import org.drools.workbench.models.datamodel.rule.IAction; import org.drools.workbench.models.datamodel.rule.IPattern; import org.drools.workbench.models.datamodel.rule.RuleMetadata; import org.drools.workbench.models.datamodel.rule.RuleModel; import org.drools.workbench.screens.guided.rule.client.editor.events.TemplateVariablesChangedEvent; import org.drools.workbench.screens.guided.rule.client.editor.plugin.RuleModellerActionPlugin; import org.drools.workbench.screens.guided.rule.client.resources.GuidedRuleEditorResources; import org.drools.workbench.screens.guided.rule.client.resources.images.GuidedRuleEditorImages508; import org.drools.workbench.screens.guided.rule.client.widget.FactTypeKnownValueChangeEvent; import org.drools.workbench.screens.guided.rule.client.widget.FactTypeKnownValueChangeHandler; import org.drools.workbench.screens.guided.rule.client.widget.RuleModellerWidget; import org.kie.workbench.common.widgets.client.datamodel.AsyncPackageDataModelOracle; import org.kie.workbench.common.widgets.client.resources.CommonAltedImages; import org.kie.workbench.common.widgets.client.ruleselector.RuleSelector; import org.uberfire.backend.vfs.Path; import org.uberfire.ext.widgets.common.client.common.ClickableLabel; import org.uberfire.ext.widgets.common.client.common.DirtyableHorizontalPane; import org.uberfire.ext.widgets.common.client.common.DirtyableVerticalPane; import org.uberfire.ext.widgets.common.client.common.SmallLabel; import org.uberfire.ext.widgets.common.client.common.popups.errors.ErrorPopup; /** * This is the parent widget that contains the model based rule builder. */ public class RuleModeller extends Composite implements RuleModelEditor { private FlexTable layout; private RuleModel model; private Collection<RuleModellerActionPlugin> actionPlugins; private AsyncPackageDataModelOracle oracle; private RuleModellerConfiguration configuration; private Map<String, Object> serviceInvocationCache = new HashMap<>(); private boolean showingOptions = false; private int currentLayoutRow = 0; private ModellerWidgetFactory widgetFactory; private EventBus eventBus; private boolean isReadOnly = false; private boolean isDSLEnabled = true; private List<RuleModellerWidget> lhsWidgets = new ArrayList<RuleModellerWidget>(); private List<RuleModellerWidget> rhsWidgets = new ArrayList<RuleModellerWidget>(); private boolean hasModifiedWidgets; private final Command onWidgetModifiedCommand = new Command() { public void execute() { hasModifiedWidgets = true; } }; private final RuleSelector ruleSelector = new RuleSelector(); //used by Guided Rule (DRL + DSLR) public RuleModeller(final RuleModel model, final Collection<RuleModellerActionPlugin> actionPlugins, final AsyncPackageDataModelOracle oracle, final ModellerWidgetFactory widgetFactory, final EventBus eventBus, final boolean isReadOnly, final boolean isDSLEnabled) { this(model, oracle, widgetFactory, RuleModellerConfiguration.getDefault(), eventBus, isReadOnly); this.actionPlugins = actionPlugins; this.isDSLEnabled = isDSLEnabled; } //used by Guided Templates public RuleModeller(final RuleModel model, final AsyncPackageDataModelOracle oracle, final ModellerWidgetFactory widgetFactory, final EventBus eventBus, final boolean isReadOnly) { this(model, Collections.emptyList(), oracle, widgetFactory, eventBus, isReadOnly, true); } //used by Guided Decision BRL Fragments public RuleModeller(final RuleModel model, final AsyncPackageDataModelOracle oracle, final ModellerWidgetFactory widgetFactory, final RuleModellerConfiguration configuration, final EventBus eventBus, final boolean isReadOnly) { this.model = model; this.oracle = oracle; this.widgetFactory = widgetFactory; this.configuration = configuration; this.eventBus = eventBus; this.isReadOnly = isReadOnly; doLayout(); } public void setRuleNamesForPackage(final Collection<String> ruleNames) { ruleSelector.setRuleNames(ruleNames, model.name); } protected void doLayout() { layout = new FlexTable(); initWidget(); layout.setStyleName("model-builder-Background"); initWidget(layout); setWidth("100%"); setHeight("100%"); } /** * This updates the widget to reflect the state of the model. */ public void initWidget() { layout.removeAllRows(); currentLayoutRow = 0; Image addPattern = GuidedRuleEditorImages508.INSTANCE.NewItem(); addPattern.setTitle(GuidedRuleEditorResources.CONSTANTS.AddAConditionToThisRule()); addPattern.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { showConditionSelector(null); } }); layout.getColumnFormatter().setWidth(0, "20px"); layout.getColumnFormatter().setWidth(1, "20px"); layout.getColumnFormatter().setWidth(2, "48px"); layout.getColumnFormatter().setWidth(4, "64px"); if (this.showExtendedRuleDropdown()) { addExtendedRuleDropdown(); } if (this.showLHS()) { layout.setWidget(currentLayoutRow, 0, new SmallLabel("<b>" + GuidedRuleEditorResources.CONSTANTS.WHEN() + "</b>")); layout.getFlexCellFormatter().setColSpan(currentLayoutRow, 0, 4); if (!lockLHS()) { layout.setWidget(currentLayoutRow, 1, addPattern); } currentLayoutRow++; renderLhs(this.model); } if (this.showRHS()) { layout.setWidget(currentLayoutRow, 0, new SmallLabel("<b>" + GuidedRuleEditorResources.CONSTANTS.THEN() + "</b>")); layout.getFlexCellFormatter().setColSpan(currentLayoutRow, 0, 4); Image addAction = GuidedRuleEditorImages508.INSTANCE.NewItem(); addAction.setTitle(GuidedRuleEditorResources.CONSTANTS.AddAnActionToThisRule()); addAction.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { showActionSelector((Widget) event.getSource(), null); } }); if (!lockRHS()) { layout.setWidget(currentLayoutRow, 1, addAction); } currentLayoutRow++; renderRhs(this.model); } if (showAttributes()) { final int optionsRowIndex = currentLayoutRow; if (!this.showingOptions) { ClickableLabel showMoreOptions = new ClickableLabel(GuidedRuleEditorResources.CONSTANTS.ShowOptions(), new ClickHandler() { public void onClick(ClickEvent event) { showingOptions = true; renderOptions(optionsRowIndex); } }); layout.setWidget(optionsRowIndex, 2, showMoreOptions); } else { renderOptions(optionsRowIndex); } } currentLayoutRow++; layout.setWidget(currentLayoutRow + 1, 3, spacerWidget()); layout.getCellFormatter().setHeight(currentLayoutRow + 1, 3, "100%"); } private void addExtendedRuleDropdown() { layout.setWidget(currentLayoutRow, 0, new SmallLabel("<b>" + GuidedRuleEditorResources.CONSTANTS.EXTENDS() + "</b>")); ruleSelector.setRuleName(model.parentName); ruleSelector.addValueChangeHandler(new ValueChangeHandler<String>() { @Override public void onValueChange(ValueChangeEvent<String> event) { model.parentName = event.getValue(); } }); layout.setWidget(currentLayoutRow, 3, ruleSelector); currentLayoutRow++; } private void renderOptions(final int optionsRowIndex) { layout.setWidget(optionsRowIndex, 2, new SmallLabel(GuidedRuleEditorResources.CONSTANTS.optionsRuleModeller())); if (!isReadOnly) { layout.setWidget(optionsRowIndex, 4, getAddAttribute()); } layout.setWidget(optionsRowIndex + 1, 3, new RuleAttributeWidget(this, this.model, isReadOnly)); } private boolean isLock(String attr) { if (isReadOnly()) { return true; } if (this.model.metadataList.length == 0) { return false; } for (RuleMetadata at : this.model.metadataList) { if (at.getAttributeName().equals(attr)) { return true; } } return false; } public boolean showRHS() { return !this.configuration.isHideRHS(); } /** * return true if we should not allow unfrozen editing of the RHS */ public boolean lockRHS() { return isLock(RuleAttributeWidget.LOCK_RHS); } public boolean showLHS() { return !this.configuration.isHideLHS(); } /** * return true if we should not allow unfrozen editing of the LHS */ public boolean lockLHS() { return isLock(RuleAttributeWidget.LOCK_LHS); } private boolean showAttributes() { return !this.configuration.isHideAttributes(); } private boolean showExtendedRuleDropdown() { return !this.configuration.isHideExtendedRuleDropdown(); } public void refreshWidget() { initWidget(); } private Widget getAddAttribute() { Image add = GuidedRuleEditorImages508.INSTANCE.NewItem(); add.setTitle(GuidedRuleEditorResources.CONSTANTS.AddAnOptionToTheRuleToModifyItsBehaviorWhenEvaluatedOrExecuted()); add.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { showAttributeSelector(); } }); return add; } protected void showAttributeSelector() { new GuidedRuleAttributeSelectorPopup(model, lockLHS(), lockRHS(), new Command() { public void execute() { refreshWidget(); } }).show(); } /** * Do all the widgets for the RHS. */ private void renderRhs(final RuleModel model) { for (int i = 0; i < model.rhs.length; i++) { DirtyableVerticalPane widget = new DirtyableVerticalPane(); widget.setWidth("100%"); IAction action = model.rhs[i]; //if lockRHS() set the widget RO, otherwise let them decide. Boolean readOnly = this.lockRHS() ? true : null; RuleModellerWidget w = getWidgetFactory().getWidget(this, eventBus, action, readOnly); w.addOnModifiedCommand(this.onWidgetModifiedCommand); widget.add(wrapRHSWidget(model, i, w)); widget.add(spacerWidget()); layout.setWidget(currentLayoutRow, 0, new DirtyableHorizontalPane()); layout.setWidget(currentLayoutRow, 1, new DirtyableHorizontalPane()); layout.setWidget(currentLayoutRow, 2, this.wrapLineNumber(i + 1, false)); layout.getFlexCellFormatter().setHorizontalAlignment(currentLayoutRow, 2, HasHorizontalAlignment.ALIGN_CENTER); layout.getFlexCellFormatter().setVerticalAlignment(currentLayoutRow, 2, HasVerticalAlignment.ALIGN_MIDDLE); layout.setWidget(currentLayoutRow, 3, widget); layout.getFlexCellFormatter().setHorizontalAlignment(currentLayoutRow, 3, HasHorizontalAlignment.ALIGN_LEFT); layout.getFlexCellFormatter().setVerticalAlignment(currentLayoutRow, 3, HasVerticalAlignment.ALIGN_TOP); layout.getFlexCellFormatter().setWidth(currentLayoutRow, 3, "100%"); layout.getRowFormatter().addStyleName(currentLayoutRow, (i % 2 == 0 ? GuidedRuleEditorResources.INSTANCE.css().evenRow() : GuidedRuleEditorResources.INSTANCE.css().oddRow())); if (!w.isFactTypeKnown()) { addInvalidPatternIcon(); addFactTypeKnownValueChangeHandler(w, currentLayoutRow); } final int index = i; if (!(this.lockRHS() || w.isReadOnly())) { this.addActionsButtonsToLayout(GuidedRuleEditorResources.CONSTANTS.AddAnActionBelow(), new ClickHandler() { public void onClick(ClickEvent event) { showActionSelector((Widget) event.getSource(), index + 1); } }, new ClickHandler() { public void onClick(ClickEvent event) { model.moveRhsItemDown(index); refreshWidget(); } }, new ClickHandler() { public void onClick(ClickEvent event) { model.moveRhsItemUp(index); refreshWidget(); } } ); } this.rhsWidgets.add(w); currentLayoutRow++; } } /** * Pops up the fact selector. */ protected void showConditionSelector(Integer position) { RuleModellerConditionSelectorPopup popup = new RuleModellerConditionSelectorPopup(model, this, position, getDataModelOracle()); popup.show(); } protected void showActionSelector(Widget w, Integer position) { RuleModellerActionSelectorPopup popup = new RuleModellerActionSelectorPopup(model, this, actionPlugins, position, getDataModelOracle()); popup.show(); } /** * Builds all the condition widgets. */ private void renderLhs(final RuleModel model) { for (int i = 0; i < model.lhs.length; i++) { DirtyableVerticalPane vert = new DirtyableVerticalPane(); vert.setWidth("100%"); //if lockLHS() set the widget RO, otherwise let them decide. Boolean readOnly = this.lockLHS() ? true : null; IPattern pattern = model.lhs[i]; final RuleModellerWidget widget = getWidgetFactory().getWidget(this, eventBus, pattern, readOnly); widget.addOnModifiedCommand(this.onWidgetModifiedCommand); vert.add(wrapLHSWidget(model, i, widget)); vert.add(spacerWidget()); layout.setWidget(currentLayoutRow, 0, new DirtyableHorizontalPane()); layout.setWidget(currentLayoutRow, 1, new DirtyableHorizontalPane()); layout.setWidget(currentLayoutRow, 2, this.wrapLineNumber(i + 1, true)); layout.getFlexCellFormatter().setHorizontalAlignment(currentLayoutRow, 2, HasHorizontalAlignment.ALIGN_CENTER); layout.getFlexCellFormatter().setVerticalAlignment(currentLayoutRow, 2, HasVerticalAlignment.ALIGN_MIDDLE); layout.setWidget(currentLayoutRow, 3, vert); layout.getFlexCellFormatter().setHorizontalAlignment(currentLayoutRow, 3, HasHorizontalAlignment.ALIGN_LEFT); layout.getFlexCellFormatter().setVerticalAlignment(currentLayoutRow, 3, HasVerticalAlignment.ALIGN_TOP); layout.getFlexCellFormatter().setWidth(currentLayoutRow, 3, "100%"); layout.getRowFormatter().addStyleName(currentLayoutRow, (i % 2 == 0 ? GuidedRuleEditorResources.INSTANCE.css().evenRow() : GuidedRuleEditorResources.INSTANCE.css().oddRow())); if (!widget.isFactTypeKnown()) { addInvalidPatternIcon(); addFactTypeKnownValueChangeHandler(widget, currentLayoutRow); } final int index = i; if (!(this.lockLHS() || widget.isReadOnly())) { this.addActionsButtonsToLayout(GuidedRuleEditorResources.CONSTANTS.AddAConditionBelow(), new ClickHandler() { public void onClick(ClickEvent event) { showConditionSelector(index + 1); } }, new ClickHandler() { public void onClick(ClickEvent event) { model.moveLhsItemDown(index); refreshWidget(); } }, new ClickHandler() { public void onClick(ClickEvent event) { model.moveLhsItemUp(index); refreshWidget(); } } ); } this.lhsWidgets.add(widget); currentLayoutRow++; } } private void addFactTypeKnownValueChangeHandler(final RuleModellerWidget widget, final int layoutRow) { widget.addFactTypeKnownValueChangeHandler(new FactTypeKnownValueChangeHandler() { @Override public void onValueChanged(FactTypeKnownValueChangeEvent factTypeKnownValueChangeEvent) { if (!widget.isFactTypeKnown()) { addInvalidPatternIcon(); } else { clearLineIcons(layoutRow, 0); } } }); } private void addInvalidPatternIcon() { final Image image = GuidedRuleEditorImages508.INSTANCE.Error(); image.setTitle(GuidedRuleEditorResources.CONSTANTS.InvalidPatternSectionDisabled()); this.addLineIcon(currentLayoutRow, 0, image); } private HTML spacerWidget() { HTML h = new HTML(" "); //NON-NLS h.setHeight("2px"); //NON-NLS return h; } private Widget wrapLineNumber(int number, boolean isLHSLine) { String id = "rhsLine"; if (isLHSLine) { id = "lhsLine"; } id += number; DirtyableHorizontalPane horiz = new DirtyableHorizontalPane(); horiz.add(new HTML("<div class='form-field' id='" + id + "'>" + number + ".</div>")); return horiz; } /** * This adds the widget to the UI, also adding the remove icon. */ private Widget wrapLHSWidget(final RuleModel model, int i, RuleModellerWidget w) { final FlexTable wrapper = new FlexTable(); final Image remove = GuidedRuleEditorImages508.INSTANCE.DeleteItemSmall(); remove.setTitle(GuidedRuleEditorResources.CONSTANTS.RemoveThisENTIREConditionAndAllTheFieldConstraintsThatBelongToIt()); final int idx = i; remove.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { if (Window.confirm(GuidedRuleEditorResources.CONSTANTS.RemoveThisEntireConditionQ())) { if (model.removeLhsItem(idx)) { refreshWidget(); //Signal possible change in Template variables TemplateVariablesChangedEvent tvce = new TemplateVariablesChangedEvent(model); eventBus.fireEventFromSource(tvce, model); } else { ErrorPopup.showMessage(GuidedRuleEditorResources.CONSTANTS.CanTRemoveThatItemAsItIsUsedInTheActionPartOfTheRule()); } } } }); wrapper.getColumnFormatter().setWidth(0, "100%"); w.setWidth("100%"); wrapper.setWidget(0, 0, w); if (!(this.lockLHS() || w.isReadOnly()) || !w.isFactTypeKnown()) { wrapper.setWidget(0, 1, remove); wrapper.getColumnFormatter().setWidth(1, "20px"); } return wrapper; } /** * This adds the widget to the UI, also adding the remove icon. */ private Widget wrapRHSWidget(final RuleModel model, int i, RuleModellerWidget w) { final FlexTable wrapper = new FlexTable(); final Image remove = GuidedRuleEditorImages508.INSTANCE.DeleteItemSmall(); remove.setTitle(GuidedRuleEditorResources.CONSTANTS.RemoveThisAction()); final int idx = i; remove.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { if (Window.confirm(GuidedRuleEditorResources.CONSTANTS.RemoveThisItem())) { model.removeRhsItem(idx); refreshWidget(); //Signal possible change in Template variables TemplateVariablesChangedEvent tvce = new TemplateVariablesChangedEvent(model); eventBus.fireEventFromSource(tvce, model); } } }); // if ( !(w instanceof ActionRetractFactWidget) ) { // w.setWidth( "100%" ); // horiz.setWidth( "100%" ); // } wrapper.getColumnFormatter().setWidth(0, "100%"); w.setWidth("100%"); wrapper.setWidget(0, 0, w); if (!(this.lockRHS() || w.isReadOnly()) || !w.isFactTypeKnown()) { wrapper.setWidget(0, 1, remove); wrapper.getColumnFormatter().setWidth(1, "20px"); } return wrapper; } private void addLineIcon(int row, int col, Image icon) { Widget widget = layout.getWidget(row, col); if (widget instanceof DirtyableHorizontalPane) { DirtyableHorizontalPane horiz = (DirtyableHorizontalPane) widget; horiz.add(icon); } } private void clearLineIcons(int row, int col) { if (layout.getCellCount(row) <= col) { return; } Widget widget = layout.getWidget(row, col); if (widget instanceof DirtyableHorizontalPane) { DirtyableHorizontalPane horiz = (DirtyableHorizontalPane) widget; horiz.clear(); } } private void clearLinesIcons(int col) { for (int i = 0; i < layout.getRowCount(); i++) { this.clearLineIcons(i, col); } } private void addActionsButtonsToLayout(String title, ClickHandler addBelowListener, ClickHandler moveDownListener, ClickHandler moveUpListener) { final DirtyableHorizontalPane hp = new DirtyableHorizontalPane(); Image addPattern = CommonAltedImages.INSTANCE.NewItemBelow(); addPattern.setTitle(title); addPattern.addClickHandler(addBelowListener); Image moveDown = CommonAltedImages.INSTANCE.MoveDown(); moveDown.setTitle(GuidedRuleEditorResources.CONSTANTS.MoveDown()); moveDown.addClickHandler(moveDownListener); Image moveUp = CommonAltedImages.INSTANCE.MoveUp(); moveUp.setTitle(GuidedRuleEditorResources.CONSTANTS.MoveUp()); moveUp.addClickHandler(moveUpListener); hp.add(addPattern); hp.add(moveDown); hp.add(moveUp); layout.setWidget(currentLayoutRow, 4, hp); layout.getFlexCellFormatter().setHorizontalAlignment(currentLayoutRow, 4, HasHorizontalAlignment.ALIGN_CENTER); layout.getFlexCellFormatter().setVerticalAlignment(currentLayoutRow, 4, HasVerticalAlignment.ALIGN_MIDDLE); } public RuleModel getModel() { return model; } /** * Returns true is a var name has already been used either by the rule, or * as a global. */ public boolean isVariableNameUsed(String name) { return model.isVariableNameUsed(name) || getDataModelOracle().isGlobalVariable(name); } public AsyncPackageDataModelOracle getDataModelOracle() { return oracle; } public ModellerWidgetFactory getWidgetFactory() { return widgetFactory; } public RuleModeller getRuleModeller() { return this; } public Map<String, Object> getServiceInvocationCache() { return serviceInvocationCache; } public boolean isTemplate() { return widgetFactory.isTemplate(); } public Path getPath() { return oracle.getResourcePath(); } public boolean isReadOnly() { return isReadOnly; } public boolean isDSLEnabled() { return isDSLEnabled; } }