/* * Copyright (c) 2010-2015 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.LoadableModel; import com.evolveum.midpoint.gui.api.model.NonEmptyLoadableModel; import com.evolveum.midpoint.gui.api.page.PageBase; import com.evolveum.midpoint.gui.api.util.WebComponentUtil; import com.evolveum.midpoint.gui.api.util.WebModelServiceUtils; import com.evolveum.midpoint.model.api.ModelExecuteOptions; import com.evolveum.midpoint.model.api.ModelService; import com.evolveum.midpoint.prism.PrismContainer; import com.evolveum.midpoint.prism.PrismContext; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.PrismReference; import com.evolveum.midpoint.prism.delta.ObjectDelta; import com.evolveum.midpoint.prism.delta.builder.DeltaBuilder; import com.evolveum.midpoint.prism.delta.builder.S_ItemEntry; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.GetOperationOptions; import com.evolveum.midpoint.schema.result.OperationResult; import com.evolveum.midpoint.schema.util.ObjectTypeUtil; 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.exception.SchemaException; import com.evolveum.midpoint.util.exception.SystemException; 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.form.DropDownFormGroup; import com.evolveum.midpoint.web.component.form.TextAreaFormGroup; import com.evolveum.midpoint.web.component.form.TextFormGroup; import com.evolveum.midpoint.web.component.wizard.WizardStep; import com.evolveum.midpoint.web.component.wizard.resource.dto.ConnectorHostTypeComparator; import com.evolveum.midpoint.web.page.admin.resources.PageResourceWizard; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorHostType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.prism.xml.ns._public.types_3.PolyStringType; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.wicket.Component; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.DropDownChoice; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.AbstractReadOnlyModel; import org.apache.wicket.model.IModel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import static org.apache.commons.collections.CollectionUtils.isEmpty; /** * @author lazyman */ public class NameStep extends WizardStep { private static final Trace LOGGER = TraceManager.getTrace(NameStep.class); private static final String DOT_CLASS = NameStep.class.getName() + "."; private static final String OPERATION_DISCOVER_CONNECTORS = DOT_CLASS + "discoverConnectors"; private static final String OPERATION_SAVE_RESOURCE = DOT_CLASS + "saveResource"; private static final String ID_NAME = "name"; private static final String ID_DESCRIPTION = "description"; private static final String ID_CONNECTOR_HOST = "connectorHost"; private static final String ID_CONNECTOR = "connector"; final private NonEmptyLoadableModel<PrismObject<ResourceType>> resourceModelRaw; final private LoadableModel<String> resourceNameModel; final private LoadableModel<String> resourceDescriptionModel; final private LoadableModel<PrismObject<ConnectorHostType>> selectedHostModel; final private LoadableModel<List<PrismObject<ConnectorType>>> allConnectorsModel; final private LoadableModel<List<PrismObject<ConnectorType>>> relevantConnectorsModel; // filtered, based on selected host final private LoadableModel<PrismObject<ConnectorType>> selectedConnectorModel; final private IModel<String> schemaChangeWarningModel; final private LoadableModel<List<PrismObject<ConnectorHostType>>> allHostsModel; // this one is not dependent on resource content final private PageResourceWizard parentPage; public NameStep(@NotNull NonEmptyLoadableModel<PrismObject<ResourceType>> modelRaw, @NotNull final PageResourceWizard parentPage) { super(parentPage); this.parentPage = parentPage; this.resourceModelRaw = modelRaw; resourceNameModel = new LoadableModel<String>() { @Override protected String load() { return PolyString.getOrig(resourceModelRaw.getObject().getName()); } }; parentPage.registerDependentModel(resourceNameModel); resourceDescriptionModel = new LoadableModel<String>() { @Override protected String load() { return resourceModelRaw.getObject().asObjectable().getDescription(); } }; parentPage.registerDependentModel(resourceDescriptionModel); allHostsModel = new LoadableModel<List<PrismObject<ConnectorHostType>>>(false) { @Override protected List<PrismObject<ConnectorHostType>> load() { return WebModelServiceUtils.searchObjects(ConnectorHostType.class, null, null, NameStep.this.parentPage); } }; selectedHostModel = new LoadableModel<PrismObject<ConnectorHostType>>(false) { @Override protected PrismObject<ConnectorHostType> load() { return getExistingConnectorHost(); } }; parentPage.registerDependentModel(selectedHostModel); allConnectorsModel = new LoadableModel<List<PrismObject<ConnectorType>>>(false) { @Override protected List<PrismObject<ConnectorType>> load() { return WebModelServiceUtils.searchObjects(ConnectorType.class, null, null, NameStep.this.parentPage); } }; parentPage.registerDependentModel(allConnectorsModel); relevantConnectorsModel = new LoadableModel<List<PrismObject<ConnectorType>>>(false) { @Override protected List<PrismObject<ConnectorType>> load() { return loadConnectors(selectedHostModel.getObject()); } }; parentPage.registerDependentModel(relevantConnectorsModel); selectedConnectorModel = new LoadableModel<PrismObject<ConnectorType>>(false) { @Override protected PrismObject<ConnectorType> load() { return getExistingConnector(); } }; parentPage.registerDependentModel(selectedConnectorModel); schemaChangeWarningModel = new AbstractReadOnlyModel<String>() { @Override public String getObject() { PrismObject<ConnectorType> selectedConnector = getConnectorDropDown().getInput().getModel().getObject(); return isConfigurationSchemaCompatible(selectedConnector) ? "" : getString("NameStep.configurationWillBeLost"); } }; initLayout(); } private void initLayout() { parentPage.addEditingEnabledBehavior(this); TextFormGroup name = new TextFormGroup(ID_NAME, resourceNameModel, createStringResource("NameStep.name"), "col-md-3", "col-md-6", true); add(name); TextAreaFormGroup description = new TextAreaFormGroup(ID_DESCRIPTION, resourceDescriptionModel, createStringResource("NameStep.description"), "col-md-3", "col-md-6", false, 3); //parentPage.addEditingEnabledBehavior(description); add(description); DropDownFormGroup<PrismObject<ConnectorHostType>> hostDropDown = createHostDropDown(); //parentPage.addEditingEnabledBehavior(hostDropDown); add(hostDropDown); DropDownFormGroup<PrismObject<ConnectorType>> connectorDropDown = createConnectorDropDown(); //parentPage.addEditingEnabledBehavior(connectorDropDown); add(connectorDropDown); } @SuppressWarnings("unchecked") private DropDownFormGroup<PrismObject<ConnectorType>> getConnectorDropDown() { return (DropDownFormGroup<PrismObject<ConnectorType>>) get(ID_CONNECTOR); } private DropDownFormGroup<PrismObject<ConnectorType>> createConnectorDropDown() { return new DropDownFormGroup<PrismObject<ConnectorType>>( ID_CONNECTOR, selectedConnectorModel, relevantConnectorsModel, new IChoiceRenderer<PrismObject<ConnectorType>>() { @Override public PrismObject<ConnectorType> getObject(String id, IModel<? extends List<? extends PrismObject<ConnectorType>>> choices) { return StringUtils.isNotBlank(id) ? choices.getObject().get(Integer.parseInt(id)) : null; } @Override public Object getDisplayValue(PrismObject<ConnectorType> object) { return WebComponentUtil.getName(object); } @Override public String getIdValue(PrismObject<ConnectorType> object, int index) { return Integer.toString(index); } }, createStringResource("NameStep.connectorType"), "col-md-3", "col-md-6", true) { @Override protected DropDownChoice<PrismObject<ConnectorType>> createDropDown(String id, IModel<List<PrismObject<ConnectorType>>> choices, IChoiceRenderer<PrismObject<ConnectorType>> renderer, boolean required) { DropDownChoice<PrismObject<ConnectorType>> choice = super.createDropDown(id, choices, renderer, required); choice.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { target.add(getConnectorDropDown().getAdditionalInfoComponent()); } }); choice.setOutputMarkupId(true); return choice; } @Override protected Component createAdditionalInfoComponent(String id) { Label l = new Label(id, schemaChangeWarningModel); l.add(new AttributeAppender("class", "text-danger")); l.setOutputMarkupId(true); return l; } }; } private boolean isConfigurationSchemaCompatible(PrismObject<ConnectorType> newConnectorObject) { if (newConnectorObject == null) { return true; // shouldn't occur } PrismContainer<?> configuration = ResourceTypeUtil.getConfigurationContainer(resourceModelRaw.getObject()); if (configuration == null || configuration.isEmpty() || configuration.getValue() == null || isEmpty(configuration.getValue().getItems())) { return true; // no config -> no loss } // for the time being let us simply compare namespaces of the current and old connector PrismObject<ConnectorType> existingConnectorObject = getExistingConnector(); if (existingConnectorObject == null) { return true; } ConnectorType existingConnector = existingConnectorObject.asObjectable(); ConnectorType newConnector = newConnectorObject.asObjectable(); return StringUtils.equals(existingConnector.getNamespace(), newConnector.getNamespace()); } @Nullable private PrismObject<ConnectorType> getSelectedConnector() { PrismObject<ConnectorType> connector = null; DropDownFormGroup<PrismObject<ConnectorType>> connectorTypeDropDown = getConnectorDropDown(); if (connectorTypeDropDown != null && connectorTypeDropDown.getInput() != null && connectorTypeDropDown.getInput().getModelObject() != null) { connector = connectorTypeDropDown.getInput().getModel().getObject(); } return connector; } @Nullable private PrismObject<ConnectorType> getExistingConnector() { return ResourceTypeUtil.getConnectorIfPresent(resourceModelRaw.getObject()); } @Nullable private PrismObject<ConnectorHostType> getExistingConnectorHost() { PrismObject<ConnectorType> connector = getExistingConnector(); if (connector == null || connector.asObjectable().getConnectorHostRef() == null) { return null; } for (PrismObject<ConnectorHostType> host : allHostsModel.getObject()) { if (connector.asObjectable().getConnectorHostRef().getOid().equals(host.getOid())) { return host; } } return null; } @NotNull private DropDownFormGroup<PrismObject<ConnectorHostType>> createHostDropDown() { return new DropDownFormGroup<PrismObject<ConnectorHostType>>(ID_CONNECTOR_HOST, selectedHostModel, allHostsModel, new IChoiceRenderer<PrismObject<ConnectorHostType>>() { @Override public PrismObject<ConnectorHostType> getObject(String id, IModel<? extends List<? extends PrismObject<ConnectorHostType>>> choices) { if (StringUtils.isBlank(id)) { return null; } return choices.getObject().get(Integer.parseInt(id)); } @Override public Object getDisplayValue(PrismObject<ConnectorHostType> object) { if (object == null) { return NameStep.this.getString("NameStep.hostNotUsed"); } return ConnectorHostTypeComparator.getUserFriendlyName(object); } @Override public String getIdValue(PrismObject<ConnectorHostType> object, int index) { return Integer.toString(index); } }, createStringResource("NameStep.connectorHost"), "col-md-3", "col-md-6", false) { @Override protected DropDownChoice<PrismObject<ConnectorHostType>> createDropDown(String id, IModel<List<PrismObject<ConnectorHostType>>> choices, IChoiceRenderer<PrismObject<ConnectorHostType>> renderer, boolean required) { DropDownChoice<PrismObject<ConnectorHostType>> choice = super.createDropDown(id, choices, renderer, required); choice.add(new AjaxFormComponentUpdatingBehavior("change") { @Override protected void onUpdate(AjaxRequestTarget target) { discoverConnectorsPerformed(target); } }); return choice; } }; } private List<PrismObject<ConnectorType>> loadConnectors(PrismObject<ConnectorHostType> host) { List<PrismObject<ConnectorType>> filtered = filterConnectors(host); Collections.sort(filtered, new Comparator<PrismObject<ConnectorType>>() { @Override public int compare(PrismObject<ConnectorType> c1, PrismObject<ConnectorType> c2) { String name1 = c1.getPropertyRealValue(ConnectorType.F_CONNECTOR_TYPE, String.class); String name2 = c2.getPropertyRealValue(ConnectorType.F_CONNECTOR_TYPE, String.class); return String.CASE_INSENSITIVE_ORDER.compare(name1, name2); } }); return filtered; } private List<PrismObject<ConnectorType>> filterConnectors(PrismObject<ConnectorHostType> host) { List<PrismObject<ConnectorType>> filtered = new ArrayList<>(); for (PrismObject<ConnectorType> connector : allConnectorsModel.getObject()) { if (isConnectorOnHost(connector, host)) { filtered.add(connector); } } return filtered; } private boolean isConnectorOnHost(PrismObject<ConnectorType> connector, @Nullable PrismObject<ConnectorHostType> host) { PrismReference connHostRef = connector.findReference(ConnectorType.F_CONNECTOR_HOST_REF); String connHostOid = connHostRef != null ? connHostRef.getOid() : null; String hostOid = host != null ? host.getOid() : null; return ObjectUtils.equals(connHostOid, hostOid); } @SuppressWarnings("unchecked") private void discoverConnectorsPerformed(AjaxRequestTarget target) { DropDownChoice<PrismObject<ConnectorHostType>> connectorHostChoice = ((DropDownFormGroup<PrismObject<ConnectorHostType>>) get(ID_CONNECTOR_HOST)).getInput(); PrismObject<ConnectorHostType> connectorHostObject = connectorHostChoice.getModelObject(); ConnectorHostType host = connectorHostObject != null ? connectorHostObject.asObjectable() : null; if (host != null) { discoverConnectors(host); allConnectorsModel.reset(); } relevantConnectorsModel.reset(); DropDownFormGroup<PrismObject<ConnectorType>> connectorDropDown = getConnectorDropDown(); PrismObject<ConnectorType> selectedConnector = connectorDropDown.getInput().getModelObject(); if (selectedConnector != null) { if (!isConnectorOnHost(selectedConnector, connectorHostObject)) { PrismObject<ConnectorType> compatibleConnector = null; for (PrismObject<ConnectorType> relevantConnector : relevantConnectorsModel.getObject()) { if (isConfigurationSchemaCompatible(relevantConnector)) { compatibleConnector = relevantConnector; break; } } selectedConnectorModel.setObject(compatibleConnector); } } target.add(connectorDropDown.getInput(), connectorDropDown.getAdditionalInfoComponent(), ((PageBase) getPage()).getFeedbackPanel()); } private void discoverConnectors(ConnectorHostType host) { PageBase page = (PageBase) getPage(); Task task = page.createSimpleTask(OPERATION_DISCOVER_CONNECTORS); OperationResult result = task.getResult(); try { ModelService model = page.getModelService(); model.discoverConnectors(host, task, result); } catch (CommonException|RuntimeException ex) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't discover connectors", ex); } finally { result.recomputeStatus(); } if (WebComponentUtil.showResultInPage(result)) { page.showResult(result); } } @Override public void applyState() { parentPage.refreshIssues(null); if (parentPage.isReadOnly() || !isComplete()) { return; } PrismContext prismContext = parentPage.getPrismContext(); Task task = parentPage.createSimpleTask(OPERATION_SAVE_RESOURCE); OperationResult result = task.getResult(); boolean saved = false; try { PrismObject<ResourceType> resource = resourceModelRaw.getObject(); PrismObject<ConnectorType> connector = getSelectedConnector(); if (connector == null) { throw new IllegalStateException("No connector selected"); // should be treated by form validation } ObjectDelta delta; final String oid = resource.getOid(); boolean isNew = oid == null; if (isNew) { resource = prismContext.createObject(ResourceType.class); ResourceType resourceType = resource.asObjectable(); resourceType.setName(PolyStringType.fromOrig(resourceNameModel.getObject())); resourceType.setDescription(resourceDescriptionModel.getObject()); resourceType.setConnectorRef(ObjectTypeUtil.createObjectRef(connector)); delta = ObjectDelta.createAddDelta(resource); } else { PrismObject<ResourceType> oldResourceObject = WebModelServiceUtils.loadObject(ResourceType.class, oid, GetOperationOptions.createRawCollection(), parentPage, parentPage.createSimpleTask("loadResource"), result); if (oldResourceObject == null) { throw new SystemException("Resource being edited (" + oid + ") couldn't be retrieved"); } ResourceType oldResource = oldResourceObject.asObjectable(); S_ItemEntry i = DeltaBuilder.deltaFor(ResourceType.class, prismContext); if (!StringUtils.equals(PolyString.getOrig(oldResource.getName()), resourceNameModel.getObject())) { i = i.item(ResourceType.F_NAME).replace(PolyString.fromOrig(resourceNameModel.getObject())); } if (!StringUtils.equals(oldResource.getDescription(), resourceDescriptionModel.getObject())) { i = i.item(ResourceType.F_DESCRIPTION).replace(resourceDescriptionModel.getObject()); } String oldConnectorOid = oldResource.getConnectorRef() != null ? oldResource.getConnectorRef().getOid() : null; String newConnectorOid = connector.getOid(); if (!StringUtils.equals(oldConnectorOid, newConnectorOid)) { i = i.item(ResourceType.F_CONNECTOR_REF).replace(ObjectTypeUtil.createObjectRef(connector).asReferenceValue()); } if (!isConfigurationSchemaCompatible(connector)) { i = i.item(ResourceType.F_CONNECTOR_CONFIGURATION).replace(); } delta = i.asObjectDelta(oid); } if (!delta.isEmpty()) { parentPage.logDelta(delta); WebModelServiceUtils.save(delta, ModelExecuteOptions.createRaw(), result, null, parentPage); parentPage.resetModels(); saved = true; } if (isNew) { parentPage.setEditedResourceOid(delta.getOid()); } } catch (RuntimeException|SchemaException ex) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save resource", ex); result.recordFatalError("Couldn't save resource, reason: " + ex.getMessage(), ex); } finally { result.computeStatusIfUnknown(); setResult(result); } if (parentPage.showSaveResultInPage(saved, result)) { parentPage.showResult(result); } } }