All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.imunity.console.tprofile.RuleComponent Maven / Gradle / Ivy

/*
 * Copyright (c) 2021 Bixbit - Krzysztof Benedyczak. All rights reserved.
 * See LICENCE.txt file for licensing information.
 */

package io.imunity.console.tprofile;

import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.Unit;
import com.vaadin.flow.component.contextmenu.MenuItem;
import com.vaadin.flow.component.dnd.DragSource;
import com.vaadin.flow.component.dnd.EffectAllowed;
import com.vaadin.flow.component.formlayout.FormLayout;
import com.vaadin.flow.component.html.Hr;
import com.vaadin.flow.component.html.Span;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.data.binder.Binder;
import io.imunity.console.tprofile.TranslationProfileEditor.DragEndEvent;
import io.imunity.console.tprofile.TranslationProfileEditor.DragStartEvent;
import io.imunity.vaadin.elements.*;
import io.imunity.vaadin.endpoint.common.ExceptionMessageHumanizer;
import io.imunity.vaadin.endpoint.common.WebSession;
import io.imunity.vaadin.endpoint.common.api.HtmlTooltipFactory;
import io.imunity.vaadin.endpoint.common.exceptions.FormValidationException;
import io.imunity.vaadin.endpoint.common.mvel.MVELExpressionField;
import pl.edu.icm.unity.base.exceptions.EngineException;
import pl.edu.icm.unity.base.message.MessageSource;
import pl.edu.icm.unity.base.translation.ProfileType;
import pl.edu.icm.unity.base.translation.TranslationAction;
import pl.edu.icm.unity.base.translation.TranslationRule;
import pl.edu.icm.unity.engine.api.authn.remote.RemotelyAuthenticatedInput;
import pl.edu.icm.unity.engine.api.bulkops.EntityMVELContextKey;
import pl.edu.icm.unity.engine.api.mvel.MVELExpressionContext;
import pl.edu.icm.unity.engine.api.translation.TranslationActionFactory;
import pl.edu.icm.unity.engine.api.translation.TranslationCondition;
import pl.edu.icm.unity.engine.api.translation.form.RegistrationMVELContextKey;
import pl.edu.icm.unity.engine.api.translation.in.InputTranslationAction;
import pl.edu.icm.unity.engine.api.translation.in.InputTranslationContextFactory;
import pl.edu.icm.unity.engine.api.translation.in.InputTranslationMVELContextKey;
import pl.edu.icm.unity.engine.api.translation.in.MappingResult;
import pl.edu.icm.unity.engine.api.translation.out.OutputTranslationMVELContextKey;
import pl.edu.icm.unity.engine.api.utils.TypesRegistryBase;
import pl.edu.icm.unity.engine.translation.in.action.IncludeInputProfileActionFactory;
import pl.edu.icm.unity.engine.translation.out.action.IncludeOutputProfileActionFactory;

import java.util.Map;
import java.util.Objects;
import java.util.Optional;

import static io.imunity.vaadin.elements.CSSVars.BASE_MARGIN;
import static io.imunity.vaadin.elements.CssClassNames.*;

public class RuleComponent extends VerticalLayout
{
	private final MessageSource msg;
	private final TypesRegistryBase> tc;
	private ActionEditor actionEditor;
	private MVELExpressionField condition;
	private MappingResultComponent mappingResultComponent;
	private final Callback callback;
	private MenuItem top;
	private MenuItem bottom;
	private final boolean editMode;
	private final ActionParameterComponentProvider actionComponentProvider;
	private FormLayout content;
	private Span info;
	private LinkButton showHide;
	private Binder binder;
	private LinkButton dragImg;
	private Component menuBar;
	private MenuItem embedProfileMenuItem;
	private final ProfileType profileType;
	private final NotificationPresenter notificationPresenter;
	private final HtmlTooltipFactory htmlTooltipFactory;

	public RuleComponent(MessageSource msg, TypesRegistryBase> tc,
						 TranslationRule toEdit, ActionParameterComponentProvider actionComponentProvider, ProfileType profileType,
						 Callback callback, NotificationPresenter notificationPresenter, HtmlTooltipFactory htmlTooltipFactory)
	{
		this.callback = callback;
		this.msg = msg;
		this.tc = tc;
		this.actionComponentProvider = actionComponentProvider;
		this.profileType = profileType;
		this.notificationPresenter = notificationPresenter;
		this.htmlTooltipFactory = htmlTooltipFactory;
		editMode = toEdit != null;
		initUI(toEdit);
	}

	private void initUI(TranslationRule toEdit)
	{
		VerticalLayout headerWrapper = new VerticalLayout();
		headerWrapper.setMargin(false);
		headerWrapper.setSpacing(false);
		headerWrapper.setPadding(false);
		
		HorizontalLayout header = new HorizontalLayout();
		header.setSizeFull();
		header.setMargin(false);
		header.setPadding(false);
	
		showHide = new LinkButton("", event -> showHideContent(!content.isVisible()));
		showHide.add(VaadinIcon.ANGLE_UP.create());
		header.add(showHide);
		header.setAlignItems(Alignment.CENTER);
		
		info = new Span("");
		info.setSizeFull();
		header.add(info);

		dragImg = new LinkButton("", e -> {});
		dragImg.add(VaadinIcon.RESIZE_H.create());
		dragImg.setSizeFull();
		dragImg.setWidth(1, Unit.EM);

		DragSource dragSource =  DragSource.create(dragImg);
		dragSource.setEffectAllowed(EffectAllowed.MOVE);
		dragSource.setDragData(this);
		dragSource.addDragStartListener(e -> WebSession.getCurrent()
				.getEventBus()
				.fireEvent(new DragStartEvent()));
		dragSource.addDragEndListener(e -> WebSession.getCurrent()
				.getEventBus()
				.fireEvent(new DragEndEvent()));
		
		
		header.add(dragImg);

		ActionMenu actionMenu = new ActionMenu();
		actionMenu.addItem(new MenuButton(msg.getMessage("TranslationProfileEditor.remove"),
				VaadinIcon.TRASH), s -> callback.remove(RuleComponent.this));
		embedProfileMenuItem = actionMenu.addItem(new MenuButton(msg.getMessage("TranslationProfileEditor.embedProfile"),
				VaadinIcon.EXPAND_SQUARE), e -> onEmbedProfileAction(embedProfileMenuItem));
		embedProfileMenuItem.setVisible(false);
		top = actionMenu.addItem(new MenuButton(msg.getMessage("TranslationProfileEditor.moveTop"),
				VaadinIcon.ARROW_UP),
				s -> callback.moveTop(RuleComponent.this));	
		bottom = actionMenu.addItem(new MenuButton(msg.getMessage("TranslationProfileEditor.moveBottom"),
				VaadinIcon.ARROW_DOWN),
				s -> callback.moveBottom(RuleComponent.this));
		menuBar = actionMenu.getTarget();

		header.add(menuBar);
				
		header.addClickListener(event ->
		{
			if (event.getClickCount() != 2)
				return;
			showHideContent(!content.isVisible());			
		});
		
		headerWrapper.add(header);

		condition = new MVELExpressionField(msg,
				msg.getMessage("MVELExpressionField.conditionDesc"),
				MVELExpressionContext.builder().withTitleKey("TranslationProfileEditor.ruleConditionTitle")
						.withEvalToKey("MVELExpressionField.evalToBoolean").withVars(getConditionContextVars())
						.build(), htmlTooltipFactory);
		
		actionEditor = new ActionEditor(msg, tc, toEdit == null ? null : toEdit.getAction(),
				actionComponentProvider, this::onActionChanged, notificationPresenter);
		
		mappingResultComponent = new MappingResultComponent(msg);	
		
		VerticalLayout main = new VerticalLayout();
		main.setMargin(false);
		main.setSpacing(false);
		main.setPadding(false);
		content = new FormLayout();
		content.setResponsiveSteps(new FormLayout.ResponsiveStep("0", 1));
		content.addFormItem(condition, msg.getMessage("TranslationProfileEditor.ruleCondition"));
		actionEditor.addToLayout(content);
		content.add(mappingResultComponent);
		showHideContent(false);
		
		main.add(headerWrapper);
		main.add(content);
		main.add(new Hr());

		info.setText(actionEditor.getStringRepresentation());
		
		binder = new Binder<>(TranslationRule.class);
		condition.configureBinding(binder, "condition", true);		
		binder.setBean(new TranslationRule(editMode? Objects.requireNonNull(toEdit).getCondition() : "true", null));
		
		add(main);
		setMargin(false);
		getStyle().set("margin-bottom", BASE_MARGIN.value());
		setPadding(false);
	}
	
	private Map getConditionContextVars()
	{
		return switch (profileType)
		{
			case BULK_ENTITY_OPS -> EntityMVELContextKey.toMap();
			case INPUT -> InputTranslationMVELContextKey.toMap();
			case OUTPUT -> OutputTranslationMVELContextKey.toMap();
			case REGISTRATION -> RegistrationMVELContextKey.toMap();
		};
	}

	private void onEmbedProfileAction(MenuItem item)
	{
		TranslationAction action;
		try
		{
			action = actionEditor.getAction();
		} catch (FormValidationException e)
		{
			notificationPresenter.showError(msg.getMessage("Generic.formError"), msg.getMessage("Generic.formErrorHint"));
			return;
		}
		String profile = action.getParameters()[0];
		ProfileType profileType = IncludeInputProfileActionFactory.NAME.equals(action.getName()) ? 
				ProfileType.INPUT : ProfileType.OUTPUT;
		callback.embedProfile(RuleComponent.this, profile, profileType);
	}
	
	private void onActionChanged(String actionStr, Optional action)
	{
		info.setText(actionStr);
		if (action.isPresent())
		{
			String actionName = action.get().getName();
			boolean enableEmbed = IncludeInputProfileActionFactory.NAME.equals(actionName)
					|| IncludeOutputProfileActionFactory.NAME.equals(actionName);
			embedProfileMenuItem.setVisible(enableEmbed);
		} else
		{
			embedProfileMenuItem.setVisible(false);
		}
	}
	
	public void setReadOnlyMode(boolean readOnly)
	{
		condition.setReadOnly(readOnly);
		dragImg.setVisible(!readOnly);
		actionEditor.setReadOnlyStyle(readOnly);
		menuBar.setVisible(!readOnly);
	}

	public TranslationRule getRule() throws FormValidationException
	{	
		if (!binder.isValid())
		{
			binder.validate();
			throw new FormValidationException();
		}
		TranslationRule rule = binder.getBean();
		rule.setTranslationAction(actionEditor.getAction());
		return rule;
	}
	
	public void setTopVisible(boolean v)
	{
		top.setVisible(v);
	}

	public void setBottomVisible(boolean v)
	{
		bottom.setVisible(v);
	}
	
	public void setFocus()
	{
		showHideContent(true);
		condition.focus();
	}

	public boolean validateRule()
	{
		boolean ok = true;
		try
		{
			actionEditor.getAction();
		} catch (Exception e)
		{
			ok = false;
		}
		
		if (!binder.isValid())
		{
			binder.validate();
			ok = false;
		}
		
		if (!ok)
			showHideContent(true);
		return ok;
	}

	public void test(RemotelyAuthenticatedInput remoteAuthnInput) 
	{
		setReadOnlyStyle(true);
		TranslationRule rule = null;
		try 
		{
			rule = getRule();
		} catch (Exception e)
		{
			actionEditor.indicateExpressionError(e);
			return;
		}
		
		Map mvelCtx = InputTranslationContextFactory.createMvelContext(remoteAuthnInput);
		TranslationCondition conditionRule = new TranslationCondition(rule.getCondition());
		try 
		{
			boolean result = conditionRule.evaluate(mvelCtx);
			setLayoutForEvaluatedCondition(result);
		} catch (EngineException e) 
		{
			indicateConditionError(e);
		}
		
		InputTranslationAction action = (InputTranslationAction) rule.getAction();
		try 
		{
			MappingResult mappingResult = action.invoke(remoteAuthnInput, mvelCtx, null);
			displayMappingResult(mappingResult);
		} catch (EngineException e) 
		{
			actionEditor.indicateExpressionError(e);
		}
	}
	
	private void setLayoutForEvaluatedCondition(boolean conditionResult)
	{
		removeRuleComponentEvaluationStyle();
		setColorForInputComponents((conditionResult ?
				TRUE_CONDITION_BACKGROUND.getName() : FALSE_CONDITION_BACKGROUND.getName()));
	}
	
	private void displayMappingResult(MappingResult mappingResult) 
	{
		mappingResultComponent.displayMappingResult(mappingResult);
		mappingResultComponent.setVisible(true);
	}
	
	private void indicateConditionError(Exception e) 
	{
		condition.addClassNameToField(ERROR_BACKGROUND.getName());
		condition.setErrorMessage(ExceptionMessageHumanizer.getHumanReadableMessage(e));
	}


	public void clearTestResult() 
	{
		removeRuleComponentEvaluationStyle();	
		hideMappingResultComponent();
		setReadOnlyStyle(false);
	}
	
	private void setColorForInputComponents(String style)
	{
		condition.addClassNameToField(style);
		actionEditor.setEvaluationStyle(style);
	}
	
	private void removeRuleComponentEvaluationStyle()
	{
		condition.removeClassNameFromField(TRUE_CONDITION_BACKGROUND.getName());
		condition.removeClassNameFromField(ERROR_BACKGROUND.getName());
		condition.removeClassNameFromField(FALSE_CONDITION_BACKGROUND.getName());
		condition.setErrorMessage(null);
		
		actionEditor.removeComponentEvaluationStyle();
	}
	
	private void hideMappingResultComponent() 
	{
		mappingResultComponent.setVisible(false);
	}
	
	private void setReadOnlyStyle(boolean readOnly)
	{
		condition.setReadOnly(readOnly);
		actionEditor.setReadOnlyStyle(readOnly);
	}
	
	private void showHideContent(boolean show)
	{
		showHide.removeAll();
		showHide.add(show ? VaadinIcon.ANGLE_UP.create() : VaadinIcon.ANGLE_DOWN.create());
		content.setVisible(show);
	}
	
	public interface Callback
	{
		boolean remove(RuleComponent rule);
		boolean moveTop(RuleComponent rule);
		boolean moveBottom(RuleComponent rule);
		boolean embedProfile(RuleComponent rule, String profile, ProfileType profileType);
	}

	public void refresh()
	{
		actionEditor.refresh();
	}

	public void setEditorContext(EditorContext editorContext)
	{
		if (editorContext.equals(EditorContext.WIZARD))
		{
			condition.getElement().getStyle().setWidth("100%");
			condition.addClassNameToField(CssClassNames.WIDTH_FULL.getName());
		}
		actionEditor.setContext(editorContext);
		
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy