/* * Copyright (c) 2010-2013 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.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.common.refinery.RefinedResourceSchemaImpl; import com.evolveum.midpoint.gui.api.model.LoadableModel; import com.evolveum.midpoint.gui.api.model.NonEmptyLoadableModel; 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.prism.util.CloneUtil; import com.evolveum.midpoint.schema.CapabilityUtil; import com.evolveum.midpoint.schema.processor.ObjectClassComplexTypeDefinition; import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition; import com.evolveum.midpoint.schema.processor.ResourceSchema; 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.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.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.capability.*; import com.evolveum.midpoint.web.component.wizard.resource.dto.Capability; import com.evolveum.midpoint.web.component.wizard.resource.dto.CapabilityDto; import com.evolveum.midpoint.web.page.admin.resources.PageResourceWizard; import com.evolveum.midpoint.web.util.InfoTooltipBehavior; import com.evolveum.midpoint.xml.ns._public.common.common_3.CapabilityCollectionType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ObjectType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowKindType; import com.evolveum.midpoint.xml.ns._public.resource.capabilities_3.*; import org.apache.wicket.AttributeModifier; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.ajax.markup.html.AjaxLink; import org.apache.wicket.behavior.AttributeAppender; import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.form.IChoiceRenderer; 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.jetbrains.annotations.NotNull; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.*; /** * @author lazyman * @author shood */ public class CapabilityStep extends WizardStep { private static final Trace LOGGER = TraceManager.getTrace(CapabilityStep.class); private static final String DOT_CLASS = CapabilityStep.class.getName() + "."; private static final String OPERATION_SAVE_CAPABILITIES = DOT_CLASS + "saveCapabilities"; private static final String ID_CAPABILITY_TABLE = "tableRows"; private static final String ID_CAPABILITY_ROW = "capabilityRow"; private static final String ID_CAPABILITY_NAME = "capabilityName"; private static final String ID_CAPABILITY_LINK = "capabilityLink"; private static final String ID_CAPABILITY_DELETE = "capabilityDelete"; private static final String ID_CAPABILITY_ADD = "capabilityAdd"; private static final String ID_CAPABILITY_CONFIG = "capabilityConfig"; private static final String ID_TOOLTIP = "tooltip"; private static final String DIALOG_SELECT_CAPABILITY = "capabilitySelectPopup"; @NotNull private final PageResourceWizard parentPage; @NotNull private final NonEmptyLoadableModel<CapabilityStepDto> dtoModel; @NotNull private final NonEmptyLoadableModel<PrismObject<ResourceType>> resourceModel; public CapabilityStep(@NotNull NonEmptyLoadableModel<PrismObject<ResourceType>> resourceModel, @NotNull PageResourceWizard parentPage) { super(parentPage); this.parentPage = parentPage; this.resourceModel = resourceModel; this.dtoModel = new NonEmptyLoadableModel<CapabilityStepDto>(false) { @Override @NotNull protected CapabilityStepDto load() { return loadDtoModel(); } }; parentPage.registerDependentModel(dtoModel); initLayout(); } @NotNull private CapabilityStepDto loadDtoModel() { ResourceType resource = resourceModel.getObject().asObjectable(); return new CapabilityStepDto(getCapabilitiesFromResource(resource)); } private List<CapabilityDto<CapabilityType>> getCapabilitiesFromResource(ResourceType resource) { List<CapabilityDto<CapabilityType>> capabilityList = new ArrayList<>(); try { Collection<Class<? extends CapabilityType>> nativeClasses = ResourceTypeUtil.getNativeCapabilityClasses(resource); List<Object> objects = ResourceTypeUtil.getAllCapabilities(resource); for (Object capabilityObject : objects) { CapabilityType capability = CapabilityUtil.asCapabilityType(capabilityObject); if (Capability.supports(capability.getClass())) { capability = fillDefaults(capability); capabilityList.add(new CapabilityDto<>(capability, nativeClasses.contains(capability.getClass()))); } else { LOGGER.warn("Capability unsupported by the Resource Wizard: {}", capability); } } } catch (SchemaException|RuntimeException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load capabilities", e); getPageBase().error(getString("CapabilityStep.message.cantLoadCaps") + e); } return capabilityList; } public static CapabilityType fillDefaults(CapabilityType capability) { CapabilityType normalized = CloneUtil.clone(capability); CapabilityUtil.fillDefaults(normalized); return normalized; } protected void initLayout() { final ListDataProvider<CapabilityDto<CapabilityType>> capabilityProvider = new ListDataProvider<>(this, new PropertyModel<List<CapabilityDto<CapabilityType>>>(dtoModel, CapabilityStepDto.F_CAPABILITIES)); WebMarkupContainer tableBody = new WebMarkupContainer(ID_CAPABILITY_TABLE); tableBody.setOutputMarkupId(true); add(tableBody); WebMarkupContainer configBody = new WebMarkupContainer(ID_CAPABILITY_CONFIG); configBody.setOutputMarkupId(true); add(configBody); DataView<CapabilityDto<CapabilityType>> capabilityDataView = new DataView<CapabilityDto<CapabilityType>>(ID_CAPABILITY_ROW, capabilityProvider) { @Override protected void populateItem(final Item<CapabilityDto<CapabilityType>> capabilityRow) { final CapabilityDto<CapabilityType> dto = capabilityRow.getModelObject(); AjaxLink name = new AjaxLink(ID_CAPABILITY_LINK) { @Override public void onClick(AjaxRequestTarget target) { editCapabilityPerformed(target, dto); } }; Label label = new Label(ID_CAPABILITY_NAME, new AbstractReadOnlyModel<String>() { @Override public String getObject() { String rv = dto.getDisplayName(); if (Boolean.FALSE.equals(dto.getCapability().isEnabled())) { rv += " " + getString("CapabilityStep.disabled"); } return rv; } }); name.add(label); capabilityRow.add(name); Label tooltipLabel = new Label(ID_TOOLTIP, new Model<>()); if (dto.getTooltipKey() != null) { tooltipLabel.add(new AttributeAppender("data-original-title", getString(dto.getTooltipKey()))); tooltipLabel.add(new InfoTooltipBehavior()); } else { tooltipLabel.setVisible(false); } tooltipLabel.setOutputMarkupId(true); tooltipLabel.setOutputMarkupPlaceholderTag(true); name.add(tooltipLabel); AjaxLink deleteLink = new AjaxLink(ID_CAPABILITY_DELETE) { @Override public void onClick(AjaxRequestTarget target) { deleteCapabilityPerformed(target, dto); } }; deleteLink.add(new VisibleEnableBehaviour() { @Override public boolean isVisible() { return !dto.isAmongNativeCapabilities() && !parentPage.isReadOnly(); } }); name.add(deleteLink); capabilityRow.add(AttributeModifier.replace("class", new AbstractReadOnlyModel<Object>() { @Override public Object getObject() { return isSelected(capabilityRow.getModelObject()) ? "success" : null; } })); } }; tableBody.add(capabilityDataView); AjaxLink addLink = new AjaxLink(ID_CAPABILITY_ADD) { @Override public void onClick(AjaxRequestTarget target) { addCapabilityPerformed(target); } }; parentPage.addEditingVisibleBehavior(addLink); add(addLink); ModalWindow dialog = new AddCapabilityDialog(DIALOG_SELECT_CAPABILITY, dtoModel) { @Override protected void addPerformed(AjaxRequestTarget target){ addCapabilitiesPerformed(target, getSelectedData()); } }; add(dialog); } private boolean isSelected(CapabilityDto capabilityDto) { return dtoModel.getObject().getSelectedDto() == capabilityDto; } private WebMarkupContainer getTable(){ return (WebMarkupContainer)get(ID_CAPABILITY_TABLE); } private WebMarkupContainer getConfigContainer(){ return (WebMarkupContainer)get(ID_CAPABILITY_CONFIG); } private void deleteCapabilityPerformed(AjaxRequestTarget target, CapabilityDto rowModel) { if (dtoModel.getObject().getSelectedDto() == rowModel) { dtoModel.getObject().setSelected(null); target.add(getConfigContainer().replaceWith(new WebMarkupContainer(ID_CAPABILITY_CONFIG))); } dtoModel.getObject().getCapabilities().remove(rowModel); target.add(getTable()); } private void addCapabilitiesPerformed(AjaxRequestTarget target, List<CapabilityDto<CapabilityType>> selected) { for (CapabilityDto<CapabilityType> dto: selected) { dtoModel.getObject().getCapabilities().add(dto); } target.add(getTable()); AddCapabilityDialog window = (AddCapabilityDialog) get(DIALOG_SELECT_CAPABILITY); window.close(target); } private void addCapabilityPerformed(AjaxRequestTarget target) { AddCapabilityDialog window = (AddCapabilityDialog)get(DIALOG_SELECT_CAPABILITY); window.updateTable(target, dtoModel); window.show(target); } @SuppressWarnings("unchecked") private void editCapabilityPerformed(final AjaxRequestTarget target, CapabilityDto<? extends CapabilityType> capability) { dtoModel.getObject().setSelected(capability); WebMarkupContainer config = getConfigContainer(); WebMarkupContainer newConfig; CapabilityType capType = capability.getCapability(); if (capType instanceof ActivationCapabilityType) { newConfig = new CapabilityActivationPanel(ID_CAPABILITY_CONFIG, new Model<>((CapabilityDto<ActivationCapabilityType>) capability), parentPage) { @Override public IModel<List<QName>> createAttributeChoiceModel(final IChoiceRenderer<QName> renderer) { LoadableModel<List<QName>> attributeChoiceModel = new LoadableModel<List<QName>>(false) { @Override protected List<QName> load() { List<QName> choices = new ArrayList<>(); PrismObject<ResourceType> resourcePrism = resourceModel.getObject(); try { ResourceSchema schema = RefinedResourceSchemaImpl.getResourceSchema(resourcePrism, getPageBase().getPrismContext()); if (schema != null) { ObjectClassComplexTypeDefinition def = schema.findDefaultObjectClassDefinition(ShadowKindType.ACCOUNT); for (ResourceAttributeDefinition attribute : def.getAttributeDefinitions()) { choices.add(attribute.getName()); } } } catch (CommonException | RuntimeException e) { LoggingUtils.logUnexpectedException(LOGGER, "Couldn't load resource schema attributes.", e); getPageBase().error("Couldn't load resource schema attributes" + e); } Collections.sort(choices, new Comparator<QName>() { @Override public int compare(QName o1, QName o2) { String s1 = (String) renderer.getDisplayValue(o1); String s2 = (String) renderer.getDisplayValue(o2); return String.CASE_INSENSITIVE_ORDER.compare(s1, s2); } }); return choices; } }; parentPage.registerDependentModel(attributeChoiceModel); return attributeChoiceModel; } }; } else if (capType instanceof ScriptCapabilityType) { newConfig = new CapabilityScriptPanel(ID_CAPABILITY_CONFIG, new Model<>((CapabilityDto<ScriptCapabilityType>) capability), getTable(), parentPage); } else if (capType instanceof CredentialsCapabilityType) { newConfig = new CapabilityCredentialsPanel(ID_CAPABILITY_CONFIG, new Model<>((CapabilityDto<CredentialsCapabilityType>)capability), getTable(), parentPage); } else { newConfig = new CapabilityValuePanel(ID_CAPABILITY_CONFIG, new Model<>((CapabilityDto<CapabilityType>) capability), getTable(), parentPage); } // TODO other specific capabilities (paged, count, ...) newConfig.setOutputMarkupId(true); config.replaceWith(newConfig); target.add(newConfig); target.add(getTable()); } @Override public void applyState() { parentPage.refreshIssues(null); if (parentPage.isReadOnly() || !isComplete()) { return; } savePerformed(); } private void savePerformed() { Task task = getPageBase().createSimpleTask(OPERATION_SAVE_CAPABILITIES); OperationResult result = task.getResult(); ModelService modelService = getPageBase().getModelService(); boolean saved = false; try { PrismObject<ResourceType> oldResource; final PrismObject<ResourceType> resourceObject = resourceModel.getObject(); ResourceType resource = resourceObject.asObjectable(); List<Object> unsupportedCapabilities = new ArrayList<>(); if (resource.getCapabilities().getConfigured() != null) { for (Object o : resource.getCapabilities().getConfigured().getAny()) { CapabilityType capabilityType = CapabilityUtil.asCapabilityType(o); if (!Capability.supports(capabilityType.getClass())) { unsupportedCapabilities.add(o); } } } // AnyArrayList that is used to implement getAny() is really strange (e.g. doesn't support iterator.remove(); // and its support for clear() is questionable) -- so let's recreate it altogether resource.getCapabilities().setConfigured(new CapabilityCollectionType()); resource.getCapabilities().getConfigured().getAny().addAll(unsupportedCapabilities); ObjectFactory capabilityFactory = new ObjectFactory(); for (CapabilityDto dto : dtoModel.getObject().getCapabilities()) { JAXBElement<? extends CapabilityType> jaxbCapability = createJAXBCapability(dto.getCapability(), capabilityFactory); if (jaxbCapability != null) { resource.getCapabilities().getConfigured().getAny().add(jaxbCapability); } } oldResource = WebModelServiceUtils.loadObject(ResourceType.class, resource.getOid(), getPageBase(), task, result); if (oldResource != null) { ObjectDelta<ResourceType> delta = parentPage.computeDiff(oldResource, resourceObject); if (!delta.isEmpty()) { parentPage.logDelta(delta); @SuppressWarnings("unchecked") Collection<ObjectDelta<? extends ObjectType>> deltas = WebComponentUtil .createDeltaCollection(delta); modelService.executeChanges(deltas, null, getPageBase().createSimpleTask(OPERATION_SAVE_CAPABILITIES), result); parentPage.resetModels(); saved = true; } } } catch (CommonException|RuntimeException e){ LoggingUtils.logUnexpectedException(LOGGER, "Couldn't save capabilities", e); result.recordFatalError("Couldn't save capabilities", e); } finally { result.computeStatusIfUnknown(); setResult(result); } if (parentPage.showSaveResultInPage(saved, result)) { getPageBase().showResult(result); } } @SuppressWarnings("unchecked") private JAXBElement<? extends CapabilityType> createJAXBCapability(CapabilityType capability, ObjectFactory factory) { for (Method method : factory.getClass().getMethods()) { Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType pt = (ParameterizedType) returnType; if (JAXBElement.class.equals(pt.getRawType()) && pt.getActualTypeArguments().length == 1 && capability.getClass().equals(pt.getActualTypeArguments()[0])) { try { return (JAXBElement<? extends CapabilityType>) method.invoke(factory, capability); } catch (IllegalAccessException|InvocationTargetException e) { throw new SystemException("Couldn't instantiate JAXBElement for capability " + capability); } } } } throw new IllegalStateException("No factory method for creating JAXBElement for capability " + capability); } }