com.evolveum.midpoint.web.component.wizard.resource.SynchronizationStep.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.web.component.wizard.resource.SynchronizationStep.java

Source

/*
 * Copyright (c) 2010-2017 Evolveum
 *
 * 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 com.evolveum.midpoint.web.component.wizard.resource;

import com.evolveum.midpoint.gui.api.model.NonEmptyLoadableModel;
import com.evolveum.midpoint.gui.api.model.NonEmptyWrapperModel;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.delta.ObjectDelta;
import com.evolveum.midpoint.schema.result.OperationResult;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.task.api.Task;
import com.evolveum.midpoint.util.exception.CommonException;
import com.evolveum.midpoint.util.logging.LoggingUtils;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.web.component.data.paging.NavigatorPanel;
import com.evolveum.midpoint.web.component.form.multivalue.MultiValueAutoCompleteTextPanel;
import com.evolveum.midpoint.web.component.form.multivalue.MultiValueTextEditPanel;
import com.evolveum.midpoint.web.component.input.ObjectReferenceChoiceRenderer;
import com.evolveum.midpoint.web.component.input.QNameChoiceRenderer;
import com.evolveum.midpoint.web.component.input.TriStateComboPanel;
import com.evolveum.midpoint.web.component.util.ListDataProvider;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.component.wizard.WizardStep;
import com.evolveum.midpoint.web.component.wizard.resource.component.synchronization.ConditionalSearchFilterEditor;
import com.evolveum.midpoint.web.component.wizard.resource.component.synchronization.SynchronizationExpressionEditor;
import com.evolveum.midpoint.web.component.wizard.resource.component.synchronization.SynchronizationReactionEditor;
import com.evolveum.midpoint.web.component.wizard.resource.dto.ResourceSynchronizationDto;
import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnChangeAjaxFormUpdatingBehavior;
import com.evolveum.midpoint.web.page.admin.resources.PageResourceWizard;
import com.evolveum.midpoint.web.session.UserProfileStorage;
import com.evolveum.midpoint.web.util.ExpressionUtil;
import com.evolveum.midpoint.web.util.InfoTooltipBehavior;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.Component;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
import org.apache.wicket.behavior.AttributeAppender;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.*;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.model.AbstractReadOnlyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.validation.IValidator;
import org.jetbrains.annotations.NotNull;

import javax.xml.namespace.QName;
import java.util.*;

/**
 * @author lazyman
 * @author shood
 */
public class SynchronizationStep extends WizardStep {

    private static final Trace LOGGER = TraceManager.getTrace(SynchronizationStep.class);

    private static final String DOT_CLASS = SynchronizationStep.class.getName() + ".";
    private static final String OPERATION_SAVE_SYNC = DOT_CLASS + "saveResourceSynchronization";

    private static final String ID_TABLE_ROWS = "tableRows";
    private static final String ID_OBJECT_SYNC_ROW = "objectSyncRow";
    private static final String ID_OBJECT_SYNC_LINK = "objectSyncLink";
    private static final String ID_OBJECT_SYNC_LABEL = "objectSyncName";
    private static final String ID_OBJECT_SYNC_DELETE = "objectSyncDelete";
    private static final String ID_PAGING = "objectSyncPaging";
    private static final String ID_OBJECT_SYNC_ADD = "objectSyncAddButton";
    private static final String ID_OBJECT_SYNC_EDITOR = "objectSyncConfig";
    private static final String ID_THIRD_ROW_CONTAINER = "thirdRowContainer";

    private static final String ID_EDITOR_LABEL = "editorLabel";
    private static final String ID_EDITOR_NAME = "editorName";
    private static final String ID_EDITOR_DESCRIPTION = "editorDescription";
    private static final String ID_EDITOR_KIND = "editorKind";
    private static final String ID_EDITOR_INTENT = "editorIntent";
    private static final String ID_EDITOR_FOCUS = "editorFocus";
    private static final String ID_EDITOR_ENABLED = "editorEnabled";
    private static final String ID_EDITOR_BUTTON_CONDITION = "editorConditionButton";
    private static final String ID_EDITOR_BUTTON_CONFIRMATION = "editorConfirmationButton";
    private static final String ID_EDITOR_OBJECT_TEMPLATE = "editorObjectTemplate";
    private static final String ID_EDITOR_RECONCILE = "editorReconcile";
    private static final String ID_EDITOR_OPPORTUNISTIC = "editorOpportunistic";
    private static final String ID_EDITOR_OBJECT_CLASS = "editorObjectClass";
    private static final String ID_EDITOR_EDITOR_CORRELATION = "editorCorrelation";
    private static final String ID_EDITOR_REACTION = "editorReaction";
    private static final String ID_T_KIND = "kindTooltip";
    private static final String ID_T_INTENT = "intentTooltip";
    private static final String ID_T_OBJ_CLASS = "objectClassTooltip";
    private static final String ID_T_FOCUS = "focusTooltip";
    private static final String ID_T_ENABLED = "enabledTooltip";
    private static final String ID_T_CONDITION = "conditionTooltip";
    private static final String ID_T_CONFIRMATION = "confirmationTooltip";
    private static final String ID_T_OBJ_TEMPLATE = "objectTemplateTooltip";
    private static final String ID_T_RECONCILE = "reconcileTooltip";
    private static final String ID_T_OPPORTUNISTIC = "opportunisticTooltip";
    private static final String ID_T_CORRELATION = "correlationTooltip";
    private static final String ID_T_REACTION = "reactionTooltip";

    @NotNull
    private final PageResourceWizard parentPage;
    @NotNull
    private final NonEmptyLoadableModel<PrismObject<ResourceType>> resourceModel;
    @NotNull
    private final NonEmptyLoadableModel<ResourceSynchronizationDto> syncDtoModel;

    public SynchronizationStep(@NotNull NonEmptyLoadableModel<PrismObject<ResourceType>> resourceModel,
            @NotNull PageResourceWizard parentPage) {
        super(parentPage);
        this.parentPage = parentPage;
        this.resourceModel = resourceModel;

        syncDtoModel = new NonEmptyLoadableModel<ResourceSynchronizationDto>(false) {
            @Override
            @NotNull
            protected ResourceSynchronizationDto load() {
                return loadResourceSynchronization();
            }
        };
        parentPage.registerDependentModel(syncDtoModel);

        initLayout();
        setOutputMarkupId(true);
    }

    @NotNull
    private ResourceSynchronizationDto loadResourceSynchronization() {

        if (resourceModel.getObject().asObjectable().getSynchronization() == null) {
            resourceModel.getObject().asObjectable().setSynchronization(new SynchronizationType());
        }

        ResourceSynchronizationDto dto = new ResourceSynchronizationDto(
                resourceModel.getObject().asObjectable().getSynchronization().getObjectSynchronization());
        dto.setObjectClassList(loadResourceObjectClassList(resourceModel, LOGGER,
                parentPage.getString("SynchronizationStep.message.errorLoadingObjectSyncList")));
        return dto;
    }

    private boolean isAnySelected() {
        return syncDtoModel.getObject().getSelected() != null;
    }

    private void initLayout() {
        final ListDataProvider<ObjectSynchronizationType> syncProvider = new ListDataProvider<>(this,
                new PropertyModel<List<ObjectSynchronizationType>>(syncDtoModel,
                        ResourceSynchronizationDto.F_OBJECT_SYNCRONIZATION_LIST));

        //first row - object sync list
        WebMarkupContainer tableBody = new WebMarkupContainer(ID_TABLE_ROWS);
        tableBody.setOutputMarkupId(true);
        add(tableBody);

        //second row - ObjectSynchronizationType editor
        WebMarkupContainer objectSyncEditor = new WebMarkupContainer(ID_OBJECT_SYNC_EDITOR);
        objectSyncEditor.setOutputMarkupId(true);
        objectSyncEditor.add(new VisibleEnableBehaviour() {

            @Override
            public boolean isVisible() {
                return isAnySelected();
            }

        });
        add(objectSyncEditor);

        //third row - container for more specific editors
        WebMarkupContainer thirdRowContainer = new WebMarkupContainer(ID_THIRD_ROW_CONTAINER);
        thirdRowContainer.setOutputMarkupId(true);
        add(thirdRowContainer);

        DataView<ObjectSynchronizationType> syncDataView = new DataView<ObjectSynchronizationType>(
                ID_OBJECT_SYNC_ROW, syncProvider, UserProfileStorage.DEFAULT_PAGING_SIZE) {

            @Override
            protected void populateItem(Item<ObjectSynchronizationType> item) {
                final ObjectSynchronizationType syncObject = item.getModelObject();

                AjaxSubmitLink link = new AjaxSubmitLink(ID_OBJECT_SYNC_LINK) {

                    @Override
                    protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                        editSyncObjectPerformed(target, syncObject);
                    }
                };
                item.add(link);

                Label label = new Label(ID_OBJECT_SYNC_LABEL, createObjectSyncTypeDisplayModel(syncObject));
                label.setOutputMarkupId(true);
                link.add(label);

                AjaxLink delete = new AjaxLink(ID_OBJECT_SYNC_DELETE) {

                    @Override
                    public void onClick(AjaxRequestTarget target) {
                        deleteSyncObjectPerformed(target, syncObject);
                    }
                };
                parentPage.addEditingVisibleBehavior(delete);
                link.add(delete);

                item.add(AttributeModifier.replace("class", new AbstractReadOnlyModel<String>() {

                    @Override
                    public String getObject() {
                        if (isSelected(syncObject)) {
                            return "success";
                        }

                        return null;
                    }
                }));

            }
        };
        tableBody.add(syncDataView);

        NavigatorPanel navigator = new NavigatorPanel(ID_PAGING, syncDataView, true);
        navigator.setOutputMarkupId(true);
        navigator.setOutputMarkupPlaceholderTag(true);
        add(navigator);

        AjaxLink add = new AjaxLink(ID_OBJECT_SYNC_ADD) {

            @Override
            public void onClick(AjaxRequestTarget target) {
                addSyncObjectPerformed(target);
            }
        };
        parentPage.addEditingVisibleBehavior(add);
        add(add);

        initObjectSyncEditor(objectSyncEditor);
    }

    @Override
    protected void onConfigure() {
        super.onConfigure();
        if (!isAnySelected()) {
            insertEmptyThirdRow();
        }
    }

    private void initObjectSyncEditor(WebMarkupContainer editor) {
        Label editorLabel = new Label(ID_EDITOR_LABEL, new AbstractReadOnlyModel<String>() {
            @Override
            public String getObject() {
                if (!isAnySelected()) {
                    return null; // shouldn't occur
                }
                String name = syncDtoModel.getObject().getSelected().getName() != null
                        ? syncDtoModel.getObject().getSelected().getName()
                        : "";
                return getString("SynchronizationStep.label.editSyncObject", name);
            }
        });
        editorLabel.setOutputMarkupId(true);
        editor.add(editorLabel);

        TextField editorName = new TextField<>(ID_EDITOR_NAME,
                new PropertyModel<String>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".name"));
        editorName.add(new UpdateNamesBehaviour());
        parentPage.addEditingEnabledBehavior(editorName);
        editor.add(editorName);

        TextArea editorDescription = new TextArea<>(ID_EDITOR_DESCRIPTION,
                new PropertyModel<String>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".description"));
        parentPage.addEditingEnabledBehavior(editorDescription);
        editor.add(editorDescription);

        DropDownChoice editorKind = new DropDownChoice<>(ID_EDITOR_KIND,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".kind"),
                WebComponentUtil.createReadonlyModelFromEnum(ShadowKindType.class), new EnumChoiceRenderer<>());
        editorKind.setNullValid(true);
        editorKind.add(new UpdateNamesBehaviour());
        parentPage.addEditingEnabledBehavior(editorKind);
        editor.add(editorKind);

        TextField editorIntent = new TextField<>(ID_EDITOR_INTENT,
                new PropertyModel<String>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".intent"));
        editorIntent.add(new UpdateNamesBehaviour());
        parentPage.addEditingEnabledBehavior(editorIntent);
        editor.add(editorIntent);

        MultiValueAutoCompleteTextPanel<QName> editorObjectClass = new MultiValueAutoCompleteTextPanel<QName>(
                ID_EDITOR_OBJECT_CLASS,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".objectClass"), true,
                parentPage.getReadOnlyModel()) {

            @Override
            protected IModel<String> createTextModel(final IModel<QName> model) {
                return new PropertyModel<>(model, "localPart");
            }

            @Override
            protected QName createNewEmptyItem() {
                return new QName("");
            }

            @Override
            protected boolean buttonsDisabled() {
                return !isAnySelected();
            }

            @Override
            protected List<QName> createObjectList() {
                return syncDtoModel.getObject().getObjectClassList();
            }

            @Override
            protected String createAutoCompleteObjectLabel(QName object) {
                return object.getLocalPart();
            }

            @Override
            protected IValidator<String> createAutoCompleteValidator() {
                return createObjectClassValidator(new AbstractReadOnlyModel<List<QName>>() {
                    @Override
                    public List<QName> getObject() {
                        return syncDtoModel.getObject().getObjectClassList();
                    }
                });
            }
        };
        parentPage.addEditingEnabledBehavior(editorObjectClass);
        editor.add(editorObjectClass);

        // TODO: switch to ObjectTypeSelectPanel
        DropDownChoice editorFocus = new DropDownChoice<>(ID_EDITOR_FOCUS,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".focusType"),
                new AbstractReadOnlyModel<List<QName>>() {

                    @Override
                    public List<QName> getObject() {
                        return WebComponentUtil.createFocusTypeList();
                    }
                }, new QNameChoiceRenderer());
        editorFocus.setNullValid(true);
        editorFocus.add(new UpdateNamesBehaviour());
        parentPage.addEditingEnabledBehavior(editorFocus);
        editor.add(editorFocus);

        CheckBox editorEnabled = new CheckBox(ID_EDITOR_ENABLED,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".enabled"));
        parentPage.addEditingEnabledBehavior(editorEnabled);
        editor.add(editorEnabled);

        AjaxSubmitLink editorCondition = new AjaxSubmitLink(ID_EDITOR_BUTTON_CONDITION) {

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                conditionEditPerformed(target);
            }
        };
        addDisableClassModifier(editorCondition);
        editor.add(editorCondition);

        AjaxSubmitLink editorConfirmation = new AjaxSubmitLink(ID_EDITOR_BUTTON_CONFIRMATION) {

            @Override
            protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
                confirmationEditPerformed(target);
            }
        };
        addDisableClassModifier(editorConfirmation);
        editor.add(editorConfirmation);

        DropDownChoice editorObjectTemplate = new DropDownChoice<>(ID_EDITOR_OBJECT_TEMPLATE,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".objectTemplateRef"),
                new AbstractReadOnlyModel<List<ObjectReferenceType>>() {

                    @Override
                    public List<ObjectReferenceType> getObject() {
                        return WebModelServiceUtils.createObjectReferenceList(ObjectTemplateType.class,
                                getPageBase(), syncDtoModel.getObject().getObjectTemplateMap());
                    }
                }, new ObjectReferenceChoiceRenderer(syncDtoModel.getObject().getObjectTemplateMap()));
        editorObjectTemplate.setNullValid(true);
        parentPage.addEditingEnabledBehavior(editorObjectTemplate);
        editor.add(editorObjectTemplate);

        CheckBox editorReconcile = new CheckBox(ID_EDITOR_RECONCILE,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".reconcile"));
        parentPage.addEditingEnabledBehavior(editorReconcile);
        editor.add(editorReconcile);

        TriStateComboPanel opportunistic = new TriStateComboPanel(ID_EDITOR_OPPORTUNISTIC,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".opportunistic"));
        parentPage.addEditingEnabledBehavior(opportunistic);
        editor.add(opportunistic);

        MultiValueTextEditPanel editorCorrelation = new MultiValueTextEditPanel<ConditionalSearchFilterType>(
                ID_EDITOR_EDITOR_CORRELATION,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".correlation"),
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED_CORRELATION), false, true,
                parentPage.getReadOnlyModel()) {

            @Override
            protected IModel<String> createTextModel(final IModel<ConditionalSearchFilterType> model) {
                return new Model<String>() {

                    @Override
                    public String getObject() {
                        StringBuilder sb = new StringBuilder();

                        ConditionalSearchFilterType searchFilter = model.getObject();
                        if (searchFilter != null && searchFilter.getDescription() != null) {
                            sb.append(searchFilter.getDescription());
                        }

                        if (sb.toString().isEmpty()) {
                            sb.append(getString("SynchronizationStep.label.notSpecified"));
                        }

                        return sb.toString();
                    }
                };
            }

            @Override
            protected ConditionalSearchFilterType createNewEmptyItem() {
                return new ConditionalSearchFilterType();
            }

            @Override
            protected void editPerformed(AjaxRequestTarget target, ConditionalSearchFilterType object) {
                correlationEditPerformed(target, object);
            }

            @Override
            protected void performAddValueHook(AjaxRequestTarget target, ConditionalSearchFilterType added) {
                parentPage.refreshIssues(target);
            }

            @Override
            protected void performRemoveValueHook(AjaxRequestTarget target,
                    ListItem<ConditionalSearchFilterType> item) {
                parentPage.refreshIssues(target);
            }

            @Override
            protected boolean buttonsDisabled() {
                return !isAnySelected();
            }
        };
        editor.add(editorCorrelation);

        MultiValueTextEditPanel editorReaction = new MultiValueTextEditPanel<SynchronizationReactionType>(
                ID_EDITOR_REACTION,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".reaction"),
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED_REACTION), false, true,
                parentPage.getReadOnlyModel()) {

            @Override
            protected IModel<String> createTextModel(final IModel<SynchronizationReactionType> model) {
                return new Model<String>() {

                    @Override
                    public String getObject() {
                        SynchronizationReactionType reaction = model.getObject();
                        if (reaction == null) {
                            return "";
                        }
                        StringBuilder sb = new StringBuilder();
                        sb.append(reaction.getName() != null ? reaction.getName() + " " : "");
                        sb.append("(");
                        if (reaction.getSituation() != null) {
                            sb.append(reaction.getSituation());
                        }
                        if (Boolean.TRUE.equals(reaction.isSynchronize()) || !reaction.getAction().isEmpty()) {
                            sb.append(" -> ");
                            if (!reaction.getAction().isEmpty()) {
                                boolean first = true;
                                for (SynchronizationActionType action : reaction.getAction()) {
                                    if (first) {
                                        first = false;
                                    } else {
                                        sb.append(", ");
                                    }
                                    sb.append(StringUtils.substringAfter(action.getHandlerUri(), "#"));
                                }
                            } else {
                                sb.append("sync"); // TODO i18n
                            }
                        }
                        sb.append(")");
                        return sb.toString();
                    }
                };
            }

            @Override
            protected SynchronizationReactionType createNewEmptyItem() {
                return new SynchronizationReactionType();
            }

            @Override
            protected void editPerformed(AjaxRequestTarget target, SynchronizationReactionType object) {
                reactionEditPerformed(target, object);
            }

            @Override
            protected void performAddValueHook(AjaxRequestTarget target, SynchronizationReactionType added) {
                parentPage.refreshIssues(target);
            }

            @Override
            protected void performRemoveValueHook(AjaxRequestTarget target,
                    ListItem<SynchronizationReactionType> item) {
                parentPage.refreshIssues(target);
            }

            @Override
            protected boolean buttonsDisabled() {
                return !isAnySelected();
            }
        };
        editorReaction.setOutputMarkupId(true);
        editor.add(editorReaction);

        Label kindTooltip = new Label(ID_T_KIND);
        kindTooltip.add(new InfoTooltipBehavior());
        editor.add(kindTooltip);

        Label intentTooltip = new Label(ID_T_INTENT);
        intentTooltip.add(new InfoTooltipBehavior());
        editor.add(intentTooltip);

        Label objClassTooltip = new Label(ID_T_OBJ_CLASS);
        objClassTooltip.add(new InfoTooltipBehavior());
        editor.add(objClassTooltip);

        Label focusTooltip = new Label(ID_T_FOCUS);
        focusTooltip.add(new InfoTooltipBehavior());
        editor.add(focusTooltip);

        Label enabledTooltip = new Label(ID_T_ENABLED);
        enabledTooltip.add(new InfoTooltipBehavior());
        editor.add(enabledTooltip);

        Label conditionTooltip = new Label(ID_T_CONDITION);
        conditionTooltip.add(new InfoTooltipBehavior());
        editor.add(conditionTooltip);

        Label confirmationTooltip = new Label(ID_T_CONFIRMATION);
        confirmationTooltip.add(new InfoTooltipBehavior());
        editor.add(confirmationTooltip);

        Label objTemplateTooltip = new Label(ID_T_OBJ_TEMPLATE);
        objTemplateTooltip.add(new InfoTooltipBehavior());
        editor.add(objTemplateTooltip);

        Label reconcileTooltip = new Label(ID_T_RECONCILE);
        reconcileTooltip.add(new InfoTooltipBehavior());
        editor.add(reconcileTooltip);

        Label opportunisticTooltip = new Label(ID_T_OPPORTUNISTIC);
        opportunisticTooltip.add(new InfoTooltipBehavior());
        editor.add(opportunisticTooltip);

        Label correlationTooltip = new Label(ID_T_CORRELATION);
        correlationTooltip.add(new InfoTooltipBehavior());
        editor.add(correlationTooltip);

        Label reactionTooltip = new Label(ID_T_REACTION);
        reactionTooltip.add(new InfoTooltipBehavior());
        editor.add(reactionTooltip);
    }

    private IModel<String> createObjectSyncTypeDisplayModel(final ObjectSynchronizationType syncObject) {
        return new AbstractReadOnlyModel<String>() {

            @Override
            public String getObject() {
                StringBuilder sb = new StringBuilder();

                if (syncObject != null) {
                    sb.append(syncObject.getName() != null ? syncObject.getName() + " " : "");
                    SchemaHandlingStep.addKindAndIntent(sb, syncObject.getKind(), syncObject.getIntent());
                    sb.append(" => ");
                    sb.append(getTypeDisplayName(ResourceTypeUtil.fillDefaultFocusType(syncObject.getFocusType())));
                }

                return sb.toString();
            }
        };
    }

    // TODO move to some utils
    private static String getTypeDisplayName(@NotNull QName name) {
        return StringUtils.removeEnd(name.getLocalPart(), "Type");
    }

    private void addDisableClassModifier(Component component) {
        component.add(new AttributeAppender("class", new AbstractReadOnlyModel<String>() {

            @Override
            public String getObject() {
                if (!isAnySelected()) {
                    return " disabled";
                }

                return null;
            }
        }));
    }

    private Component getSyncObjectTable() {
        return get(ID_TABLE_ROWS);
    }

    private Component getNavigator() {
        return get(ID_PAGING);
    }

    private Component getSyncObjectEditor() {
        return get(ID_OBJECT_SYNC_EDITOR);
    }

    public Component getReactionList() {
        return get(createComponentPath(ID_OBJECT_SYNC_EDITOR, ID_EDITOR_REACTION));
    }

    public Component getCorrelationList() {
        return get(createComponentPath(ID_OBJECT_SYNC_EDITOR, ID_EDITOR_EDITOR_CORRELATION));
    }

    private Component getThirdRowContainer() {
        return get(ID_THIRD_ROW_CONTAINER);
    }

    private void insertEmptyThirdRow() {
        getThirdRowContainer().replaceWith(new WebMarkupContainer(ID_THIRD_ROW_CONTAINER));
    }

    private void conditionEditPerformed(AjaxRequestTarget target) {
        WebMarkupContainer newContainer = new SynchronizationExpressionEditor(ID_THIRD_ROW_CONTAINER,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".condition"),
                parentPage) {

            @Override
            public String getLabel() {
                return "SynchronizationExpressionEditor.label.condition";
            }
        };
        getThirdRowContainer().replaceWith(newContainer);
        resetSelections(target);
        target.add(getThirdRowContainer(), get(ID_OBJECT_SYNC_EDITOR), getPageBase().getFeedbackPanel());
    }

    private void confirmationEditPerformed(AjaxRequestTarget target) {
        WebMarkupContainer newContainer = new SynchronizationExpressionEditor(ID_THIRD_ROW_CONTAINER,
                new PropertyModel<>(syncDtoModel, ResourceSynchronizationDto.F_SELECTED + ".confirmation"),
                parentPage) {

            @Override
            public String getLabel() {
                return "SynchronizationExpressionEditor.label.confirmation";
            }
        };
        getThirdRowContainer().replaceWith(newContainer);
        resetSelections(target);
        target.add(getThirdRowContainer(), get(ID_OBJECT_SYNC_EDITOR), getPageBase().getFeedbackPanel());
    }

    private void correlationEditPerformed(AjaxRequestTarget target,
            @NotNull ConditionalSearchFilterType condition) {
        if (condition.getCondition() == null) {
            condition.setCondition(new ExpressionType()); // removed at save
        }
        resetSelections(target);
        syncDtoModel.getObject().setSelectedCorrelation(condition);
        WebMarkupContainer newContainer = new ConditionalSearchFilterEditor(ID_THIRD_ROW_CONTAINER,
                new NonEmptyWrapperModel<>(new Model<>(condition)), parentPage);
        getThirdRowContainer().replaceWith(newContainer);

        target.add(getThirdRowContainer(), get(ID_OBJECT_SYNC_EDITOR), getPageBase().getFeedbackPanel());
    }

    private void reactionEditPerformed(AjaxRequestTarget target, SynchronizationReactionType reaction) {
        WebMarkupContainer newContainer = new SynchronizationReactionEditor(ID_THIRD_ROW_CONTAINER,
                new Model<>(reaction), this, parentPage);
        getThirdRowContainer().replaceWith(newContainer);

        for (SynchronizationActionType action : reaction.getAction()) {
            if (action.getRef() != null) {
                warn(getString("SynchronizationStep.message.unsupportedActionFormat"));
                break;
            }
        }

        resetSelections(target);
        syncDtoModel.getObject().setSelectedReaction(reaction);

        target.add(getThirdRowContainer(), get(ID_OBJECT_SYNC_EDITOR), getPageBase().getFeedbackPanel());
    }

    @Override
    public void applyState() {
        parentPage.refreshIssues(null);
        if (parentPage.isReadOnly() || !isComplete()) {
            return;
        }
        savePerformed();
        insertEmptyThirdRow();
        resetSelections(null);
    }

    private void savePerformed() {
        PrismObject<ResourceType> oldResource;
        PrismObject<ResourceType> newResource = resourceModel.getObject();
        Task task = getPageBase().createSimpleTask(OPERATION_SAVE_SYNC);
        OperationResult result = task.getResult();
        ModelService modelService = getPageBase().getModelService();
        boolean saved = false;

        removeEmptyContainers(newResource.asObjectable());

        try {
            oldResource = WebModelServiceUtils.loadObject(ResourceType.class, newResource.getOid(), getPageBase(),
                    task, result);
            if (oldResource != null) {
                ObjectDelta<ResourceType> delta = parentPage.computeDiff(oldResource, newResource);
                if (!delta.isEmpty()) {
                    parentPage.logDelta(delta);
                    Collection<ObjectDelta<? extends ObjectType>> deltas = WebComponentUtil
                            .createDeltaCollection(delta);
                    modelService.executeChanges(deltas, null, getPageBase().createSimpleTask(OPERATION_SAVE_SYNC),
                            result);
                    parentPage.resetModels();
                    syncDtoModel.reset();
                    saved = true;
                }
            }
        } catch (CommonException | RuntimeException e) {
            LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save resource synchronization.", e);
            result.recordFatalError(getString("SynchronizationStep.message.cantSave", e));
        } finally {
            result.computeStatusIfUnknown();
            setResult(result);
        }

        if (parentPage.showSaveResultInPage(saved, result)) {
            getPageBase().showResult(result);
        }
    }

    private void removeEmptyContainers(ResourceType resourceType) {
        if (resourceType.getSynchronization() == null) {
            return;
        }

        for (ObjectSynchronizationType objectSync : resourceType.getSynchronization().getObjectSynchronization()) {
            objectSync.getObjectClass().removeIf(name -> name == null || StringUtils.isBlank(name.getLocalPart()));
            if (objectSync.getCondition() != null && ExpressionUtil.isEmpty(objectSync.getCondition())) {
                objectSync.setCondition(null);
            }
            if (objectSync.getConfirmation() != null && ExpressionUtil.isEmpty(objectSync.getConfirmation())) {
                objectSync.setConfirmation(null);
            }
            for (ConditionalSearchFilterType correlationFilter : objectSync.getCorrelation()) {
                if (correlationFilter.getCondition() != null
                        && ExpressionUtil.isEmpty(correlationFilter.getCondition())) {
                    correlationFilter.setCondition(null);
                }
            }
        }
    }

    private void editSyncObjectPerformed(AjaxRequestTarget target, ObjectSynchronizationType syncObject) {
        boolean wasAnySelected = isAnySelected();
        syncDtoModel.getObject().setSelected(syncObject);
        insertEmptyThirdRow();
        resetSelections(target);
        if (wasAnySelected) {
            target.add(getSyncObjectTable(), getNavigator(), getSyncObjectEditor(), getThirdRowContainer());
        } else {
            target.add(this);
        }
    }

    private void deleteSyncObjectPerformed(AjaxRequestTarget target, ObjectSynchronizationType syncObject) {
        if (isSelected(syncObject)) {
            syncDtoModel.getObject().setSelected(null);
            insertEmptyThirdRow();
            resetSelections(target);
            target.add(getThirdRowContainer());
        }

        ArrayList<ObjectSynchronizationType> list = (ArrayList<ObjectSynchronizationType>) syncDtoModel.getObject()
                .getObjectSynchronizationList();
        list.remove(syncObject);
        if (list.isEmpty()) {
            insertEmptyThirdRow();
            resetSelections(target);
            target.add(getThirdRowContainer());
        }

        target.add(getSyncObjectEditor(), getSyncObjectTable(), getNavigator());
        parentPage.refreshIssues(target);
    }

    private boolean isSelected(ObjectSynchronizationType syncObject) {
        return syncDtoModel.getObject().getSelected() == syncObject;
    }

    private void addSyncObjectPerformed(AjaxRequestTarget target) {
        ObjectSynchronizationType syncObject = new ObjectSynchronizationType();
        syncObject.setEnabled(true);
        //syncObject.setName(generateName(getString("SynchronizationStep.label.newObjectType")));

        resourceModel.getObject().asObjectable().getSynchronization().getObjectSynchronization().add(syncObject);
        editSyncObjectPerformed(target, syncObject);
        parentPage.refreshIssues(target);
    }

    private class UpdateNamesBehaviour extends EmptyOnChangeAjaxFormUpdatingBehavior {
        @Override
        protected void onUpdate(AjaxRequestTarget target) {
            target.add(getSyncObjectTable(), getSyncObjectEditor().get(ID_EDITOR_LABEL));
            parentPage.refreshIssues(target);
        }
    }

    private void resetSelections(AjaxRequestTarget target) {
        ResourceSynchronizationDto dto = syncDtoModel.getObject();
        if (dto.getSelectedCorrelation() != null) {
            dto.setSelectedCorrelation(null);
            if (target != null) {
                target.add(getCorrelationList());
            }
        }
        if (dto.getSelectedReaction() != null) {
            dto.setSelectedReaction(null);
            if (target != null) {
                target.add(getReactionList());
            }
        }
    }

    private String generateName(String prefix) {
        List<String> existing = new ArrayList<>();
        for (ObjectSynchronizationType sync : syncDtoModel.getObject().getObjectSynchronizationList()) {
            CollectionUtils.addIgnoreNull(existing, sync.getName());
        }
        return SchemaHandlingStep.generateName(existing, prefix);
    }

}