/*
* Copyright (c) 2010-2014 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.component.schemahandling;
import com.evolveum.midpoint.gui.api.component.BasePanel;
import com.evolveum.midpoint.gui.api.model.NonEmptyModel;
import com.evolveum.midpoint.gui.api.util.WebComponentUtil;
import com.evolveum.midpoint.prism.PrismObject;
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.processor.ResourceSchemaImpl;
import com.evolveum.midpoint.schema.util.ResourceTypeUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
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.AjaxSubmitButton;
import com.evolveum.midpoint.web.component.form.multivalue.MultiValueTextEditPanel;
import com.evolveum.midpoint.web.component.form.multivalue.MultiValueTextPanel;
import com.evolveum.midpoint.web.component.input.QNameChoiceRenderer;
import com.evolveum.midpoint.web.component.input.QNameEditorPanel;
import com.evolveum.midpoint.web.component.util.VisibleEnableBehaviour;
import com.evolveum.midpoint.web.component.wizard.WizardUtil;
import com.evolveum.midpoint.web.component.wizard.resource.SchemaHandlingStep;
import com.evolveum.midpoint.web.component.wizard.resource.component.schemahandling.modal.LimitationsEditorDialog;
import com.evolveum.midpoint.web.component.wizard.resource.component.schemahandling.modal.MappingEditorDialog;
import com.evolveum.midpoint.web.component.wizard.resource.dto.MappingTypeDto;
import com.evolveum.midpoint.web.page.admin.configuration.component.EmptyOnChangeAjaxFormUpdatingBehavior;
import com.evolveum.midpoint.web.page.admin.resources.PageResourceWizard;
import com.evolveum.midpoint.web.page.admin.resources.PageResources;
import com.evolveum.midpoint.web.util.InfoTooltipBehavior;
import com.evolveum.midpoint.xml.ns._public.common.common_3.*;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
import org.apache.wicket.RestartResponseException;
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.extensions.ajax.markup.html.modal.ModalWindow;
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.model.*;
import org.jetbrains.annotations.NotNull;
import org.w3c.dom.Element;
import javax.xml.namespace.QName;
import java.util.ArrayList;
import java.util.List;
/**
* @author shood
* */
public class ResourceAssociationEditor extends BasePanel<ResourceObjectAssociationType> {
private static final Trace LOGGER = TraceManager.getTrace(ResourceAssociationEditor.class);
private static final String ID_LABEL = "label";
private static final String ID_KIND = "kind";
private static final String ID_INTENT = "intent";
private static final String ID_DIRECTION = "direction";
private static final String ID_ASSOCIATION_ATTRIBUTE = "associationAttribute";
private static final String ID_VALUE_ATTRIBUTE = "valueAttribute";
private static final String ID_EXPLICIT_REF_INTEGRITY = "explicitRefIntegrity";
private static final String ID_ASSOCIATION_ATTRIBUTE_PANEL = "associationAttributePanel";
private static final String ID_DISPLAY_NAME = "displayName";
private static final String ID_DESCRIPTION = "description";
private static final String ID_EXCLUSIVE_STRONG = "exclusiveStrong";
private static final String ID_TOLERANT = "tolerant";
private static final String ID_TOLERANT_VP = "tolerantValuePattern";
private static final String ID_INTOLERANT_VP = "intolerantValuePattern";
private static final String ID_FETCH_STRATEGY = "fetchStrategy";
private static final String ID_INBOUND = "inbound";
private static final String ID_OUTBOUND_LABEL = "outboundLabel";
private static final String ID_BUTTON_OUTBOUND = "buttonOutbound";
private static final String ID_DELETE_OUTBOUND = "deleteOutbound";
private static final String ID_BUTTON_LIMITATIONS = "buttonLimitations";
private static final String ID_MODAL_LIMITATIONS = "limitationsEditor";
private static final String ID_MODAL_INBOUND = "inboundEditor";
private static final String ID_MODAL_OUTBOUND = "outboundEditor";
private static final String ID_T_LIMITATIONS = "limitationsTooltip";
private static final String ID_T_EXCLUSIVE_STRONG = "exclusiveStrongTooltip";
private static final String ID_T_TOLERANT = "tolerantTooltip";
private static final String ID_T_TOLERANT_VP = "tolerantVPTooltip";
private static final String ID_T_INTOLERANT_VP = "intolerantVPTooltip";
private static final String ID_T_FETCH = "fetchStrategyTooltip";
private static final String ID_T_MATCHING_RULE = "matchingRuleTooltip";
private static final String ID_T_OUTBOUND = "outboundTooltip";
private static final String ID_T_INBOUND = "inboundTooltip";
private static final String ID_T_KIND = "kindTooltip";
private static final String ID_T_INTENT = "intentTooltip";
private static final String ID_T_DIRECTION = "directionTooltip";
private static final String ID_T_ASSOCIATION_ATTRIBUTE = "associationAttributeTooltip";
private static final String ID_T_VALUE_ATTRIBUTE = "valueAttributeTooltip";
private static final String ID_T_EXPLICIT_REF_INTEGRITY = "explicitRefIntegrityTooltip";
private PrismObject<ResourceType> resource;
private ResourceObjectTypeDefinitionType objectType;
@NotNull final private SchemaHandlingStep parentStep;
public ResourceAssociationEditor(String id, IModel<ResourceObjectAssociationType> model,
ResourceObjectTypeDefinitionType objectType, PrismObject<ResourceType> resource, SchemaHandlingStep parentStep,
NonEmptyModel<Boolean> readOnlyModel) {
super(id, model);
this.resource = resource;
this.objectType = objectType;
this.parentStep = parentStep;
initLayout(readOnlyModel);
}
protected void initLayout(NonEmptyModel<Boolean> readOnlyModel) {
Label label = new Label(ID_LABEL, new ResourceModel("ResourceAssociationEditor.label.edit"));
label.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(label);
DropDownChoice kind = new DropDownChoice<>(ID_KIND,
new PropertyModel<ShadowKindType>(getModel(), "kind"),
WebComponentUtil.createReadonlyModelFromEnum(ShadowKindType.class),
new EnumChoiceRenderer<ShadowKindType>(this));
kind.setNullValid(false);
kind.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(kind);
MultiValueTextPanel intent = new MultiValueTextPanel<>(ID_INTENT,
new PropertyModel<List<String>>(getModel(), "intent"), readOnlyModel, true);
intent.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(intent);
DropDownChoice direction = new DropDownChoice<>(ID_DIRECTION,
new PropertyModel<ResourceObjectAssociationDirectionType>(getModel(), "direction"),
WebComponentUtil.createReadonlyModelFromEnum(ResourceObjectAssociationDirectionType.class),
new EnumChoiceRenderer<ResourceObjectAssociationDirectionType>(this));
direction.setNullValid(true);
direction.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(direction);
DropDownChoice associationAttribute = new DropDownChoice<>(ID_ASSOCIATION_ATTRIBUTE,
new PropertyModel<QName>(getModel(), "associationAttribute"),
new AbstractReadOnlyModel<List<QName>>() {
@Override
public List<QName> getObject() {
return loadObjectReferences(false);
}
}, new QNameChoiceRenderer(true));
associationAttribute.setNullValid(true);
associationAttribute.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(associationAttribute);
DropDownChoice valueAttribute = new DropDownChoice<>(ID_VALUE_ATTRIBUTE,
new PropertyModel<QName>(getModel(), "valueAttribute"),
new AbstractReadOnlyModel<List<QName>>() {
@Override
public List<QName> getObject() {
return loadObjectReferences(false);
}
}, new QNameChoiceRenderer(true));
valueAttribute.setNullValid(true);
valueAttribute.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(valueAttribute);
CheckBox explicitRefIntegrity = new CheckBox(ID_EXPLICIT_REF_INTEGRITY,
new PropertyModel<Boolean>(getModel(), "explicitReferentialIntegrity"));
explicitRefIntegrity.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(explicitRefIntegrity);
QNameEditorPanel nonSchemaRefPanel = new QNameEditorPanel(ID_ASSOCIATION_ATTRIBUTE_PANEL, new PropertyModel<ItemPathType>(getModel(), "ref"),
"SchemaHandlingStep.association.label.associationName", "SchemaHandlingStep.association.tooltip.associationLocalPart",
"SchemaHandlingStep.association.label.associationNamespace", "SchemaHandlingStep.association.tooltip.associationNamespace", true, true) {
@Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(parentStep.getAssociationList());
((PageResourceWizard) getPageBase()).refreshIssues(target);
}
};
nonSchemaRefPanel.setOutputMarkupId(true);
nonSchemaRefPanel.setOutputMarkupPlaceholderTag(true);
nonSchemaRefPanel.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(nonSchemaRefPanel);
TextField displayName = new TextField<>(ID_DISPLAY_NAME, new PropertyModel<String>(getModel(), "displayName"));
displayName.add(new EmptyOnChangeAjaxFormUpdatingBehavior() {
@Override
protected void onUpdate(AjaxRequestTarget target) {
target.add(parentStep.getAssociationList());
}
});
displayName.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(displayName);
TextArea description = new TextArea<>(ID_DESCRIPTION, new PropertyModel<String>(getModel(), "description"));
description.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(description);
AjaxLink limitations = new AjaxLink(ID_BUTTON_LIMITATIONS) {
@Override
public void onClick(AjaxRequestTarget target) {
limitationsEditPerformed(target);
}
};
add(limitations);
CheckBox exclusiveStrong = new CheckBox(ID_EXCLUSIVE_STRONG, new PropertyModel<Boolean>(getModel(), "exclusiveStrong"));
exclusiveStrong.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(exclusiveStrong);
CheckBox tolerant = new CheckBox(ID_TOLERANT, new PropertyModel<Boolean>(getModel(), "tolerant"));
tolerant.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(tolerant);
MultiValueTextPanel tolerantVP = new MultiValueTextPanel<>(ID_TOLERANT_VP,
new PropertyModel<List<String>>(getModel(), "tolerantValuePattern"), readOnlyModel, true);
tolerantVP.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(tolerantVP);
MultiValueTextPanel intolerantVP = new MultiValueTextPanel<>(ID_INTOLERANT_VP,
new PropertyModel<List<String>>(getModel(), "intolerantValuePattern"), readOnlyModel, true);
intolerantVP.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(intolerantVP);
DropDownChoice fetchStrategy = new DropDownChoice<>(ID_FETCH_STRATEGY,
new PropertyModel<AttributeFetchStrategyType>(getModel(), "fetchStrategy"),
WebComponentUtil.createReadonlyModelFromEnum(AttributeFetchStrategyType.class),
new EnumChoiceRenderer<AttributeFetchStrategyType>(this));
fetchStrategy.setNullValid(true);
fetchStrategy.add(WebComponentUtil.enabledIfFalse(readOnlyModel));
add(fetchStrategy);
AttributeEditorUtils.addMatchingRuleFields(this, readOnlyModel);
VisibleEnableBehaviour showIfEditingOrOutboundExists = AttributeEditorUtils.createShowIfEditingOrOutboundExists(getModel(), readOnlyModel);
TextField outboundLabel = new TextField<>(ID_OUTBOUND_LABEL,
new AbstractReadOnlyModel<String>() {
@Override
public String getObject() {
ResourceObjectAssociationType association = getModel().getObject();
if(association == null){
return null;
}
return MappingTypeDto.createMappingLabel(association.getOutbound(), LOGGER, getPageBase().getPrismContext(),
getString("MappingType.label.placeholder"), getString("MultiValueField.nameNotSpecified"));
}
});
outboundLabel.setOutputMarkupId(true);
outboundLabel.setEnabled(false);
outboundLabel.add(showIfEditingOrOutboundExists);
add(outboundLabel);
AjaxSubmitButton outbound = new AjaxSubmitButton(ID_BUTTON_OUTBOUND) {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
outboundEditPerformed(target);
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(parentStep.getPageBase().getFeedbackPanel());
}
};
outbound.setOutputMarkupId(true);
outbound.add(showIfEditingOrOutboundExists);
add(outbound);
AjaxSubmitLink deleteOutbound = new AjaxSubmitLink(ID_DELETE_OUTBOUND) {
@Override
protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
deleteOutboundPerformed(target);
}
@Override
protected void onError(AjaxRequestTarget target, Form<?> form) {
target.add(parentStep.getPageBase().getFeedbackPanel());
}
};
deleteOutbound.setOutputMarkupId(true);
deleteOutbound.add(WebComponentUtil.visibleIfFalse(readOnlyModel));
add(deleteOutbound);
MultiValueTextEditPanel inbound = new MultiValueTextEditPanel<MappingType>(ID_INBOUND,
new PropertyModel<List<MappingType>>(getModel(), "inbound"), null, false, true, readOnlyModel) {
@Override
protected IModel<String> createTextModel(final IModel<MappingType> model) {
return new Model<String>() {
@Override
public String getObject() {
return MappingTypeDto.createMappingLabel(model.getObject(), LOGGER, getPageBase().getPrismContext(),
getString("MappingType.label.placeholder"), getString("MultiValueField.nameNotSpecified"));
}
};
}
@Override
protected MappingType createNewEmptyItem(){
return WizardUtil.createEmptyMapping();
}
@Override
protected void performAddValueHook(AjaxRequestTarget target, MappingType added) {
target.add(parentStep.getAssociationList());
target.add(parentStep.getAttributeList()); // because of marking duplicates
((PageResourceWizard) getPageBase()).refreshIssues(target);
}
@Override
protected void performRemoveValueHook(AjaxRequestTarget target, ListItem<MappingType> item) {
target.add(parentStep.getAssociationList());
target.add(parentStep.getAttributeList()); // because of marking duplicates
((PageResourceWizard) getPageBase()).refreshIssues(target);
}
@Override
protected void editPerformed(AjaxRequestTarget target, MappingType object){
inboundEditPerformed(target, object);
}
};
inbound.setOutputMarkupId(true);
add(inbound);
Label kindTooltip = new Label(ID_T_KIND);
kindTooltip.add(new InfoTooltipBehavior());
add(kindTooltip);
Label intentTooltip = new Label(ID_T_INTENT);
intentTooltip.add(new InfoTooltipBehavior());
add(intentTooltip);
Label directionTooltip = new Label(ID_T_DIRECTION);
directionTooltip.add(new InfoTooltipBehavior());
add(directionTooltip);
Label assAttributeTooltip = new Label(ID_T_ASSOCIATION_ATTRIBUTE);
assAttributeTooltip.add(new InfoTooltipBehavior());
add(assAttributeTooltip);
Label valueAttributeTooltip = new Label(ID_T_VALUE_ATTRIBUTE);
valueAttributeTooltip.add(new InfoTooltipBehavior());
add(valueAttributeTooltip);
Label integrityTooltip = new Label(ID_T_EXPLICIT_REF_INTEGRITY);
integrityTooltip.add(new InfoTooltipBehavior());
add(integrityTooltip);
Label limitationsTooltip = new Label(ID_T_LIMITATIONS);
limitationsTooltip.add(new InfoTooltipBehavior());
add(limitationsTooltip);
Label exclusiveStrongTooltip = new Label(ID_T_EXCLUSIVE_STRONG);
exclusiveStrongTooltip.add(new InfoTooltipBehavior());
add(exclusiveStrongTooltip);
Label tolerantTooltip = new Label(ID_T_TOLERANT);
tolerantTooltip.add(new InfoTooltipBehavior());
add(tolerantTooltip);
Label tolerantVPTooltip = new Label(ID_T_TOLERANT_VP);
tolerantVPTooltip.add(new InfoTooltipBehavior());
add(tolerantVPTooltip);
Label intolerantVPTooltip = new Label(ID_T_INTOLERANT_VP);
intolerantVPTooltip.add(new InfoTooltipBehavior());
add(intolerantVPTooltip);
Label fetchTooltip = new Label(ID_T_FETCH);
fetchTooltip.add(new InfoTooltipBehavior());
add(fetchTooltip);
Label matchingRuleTooltip = new Label(ID_T_MATCHING_RULE);
matchingRuleTooltip.add(new InfoTooltipBehavior());
add(matchingRuleTooltip);
Label outboundTooltip = new Label(ID_T_OUTBOUND);
outboundTooltip.add(new InfoTooltipBehavior());
add(outboundTooltip);
Label inboundTooltip = new Label(ID_T_INBOUND);
inboundTooltip.add(new InfoTooltipBehavior());
add(inboundTooltip);
initModals(readOnlyModel);
}
private void initModals(NonEmptyModel<Boolean> readOnlyModel) {
ModalWindow limitationsEditor = new LimitationsEditorDialog(ID_MODAL_LIMITATIONS,
new PropertyModel<List<PropertyLimitationsType>>(getModel(), "limitations"), readOnlyModel);
add(limitationsEditor);
ModalWindow inboundEditor = new MappingEditorDialog(ID_MODAL_INBOUND, null, readOnlyModel) {
@Override
public void updateComponents(AjaxRequestTarget target){
target.add(ResourceAssociationEditor.this.get(ID_INBOUND), parentStep.getAssociationList());
}
};
add(inboundEditor);
ModalWindow outboundEditor = new MappingEditorDialog(ID_MODAL_OUTBOUND, null, readOnlyModel) {
@Override
public void updateComponents(AjaxRequestTarget target) {
target.add(ResourceAssociationEditor.this.get(ID_OUTBOUND_LABEL), ResourceAssociationEditor.this.get(ID_BUTTON_OUTBOUND), parentStep.getAssociationList());
}
};
add(outboundEditor);
}
private List<QName> loadObjectReferences(boolean restrictObjectClass){
List<QName> references = new ArrayList<>();
ResourceSchema schema = loadResourceSchema();
if (schema == null) {
return references;
}
for(ObjectClassComplexTypeDefinition def: schema.getObjectClassDefinitions()){
if(restrictObjectClass){
if(objectType != null && def.getTypeName().equals(objectType.getObjectClass())){
for(ResourceAttributeDefinition attributeDefinition : def.getAttributeDefinitions()) {
references.add(attributeDefinition.getName());
}
}
} else {
for(ResourceAttributeDefinition attributeDefinition : def.getAttributeDefinitions()) {
references.add(attributeDefinition.getName());
}
}
}
return references;
}
private ResourceSchema loadResourceSchema() {
if(resource != null){
Element xsdSchema = ResourceTypeUtil.getResourceXsdSchema(resource);
if (xsdSchema == null) {
return null;
}
try {
return ResourceSchemaImpl.parse(xsdSchema, resource.toString(), getPageBase().getPrismContext());
} catch (SchemaException|RuntimeException e) {
LoggingUtils.logUnexpectedException(LOGGER, "Couldn't parse resource schema.", e);
getSession().error(getString("ResourceAssociationEditor.message.cantParseSchema") + " " + e.getMessage());
throw new RestartResponseException(PageResources.class);
}
}
return null;
}
private String prepareReferenceDisplayValue(QName object){
StringBuilder sb = new StringBuilder();
if(object != null){
sb.append(object.getLocalPart());
if(object.getNamespaceURI() != null){
sb.append(" (");
String[] ns = object.getNamespaceURI().split("/");
sb.append(ns[ns.length-1]);
sb.append(")");
}
}
return sb.toString();
}
private void limitationsEditPerformed(AjaxRequestTarget target){
LimitationsEditorDialog window = (LimitationsEditorDialog)get(ID_MODAL_LIMITATIONS);
window.show(target);
}
private void deleteOutboundPerformed(AjaxRequestTarget target) {
ResourceObjectAssociationType def = getModelObject();
def.setOutbound(null);
target.add(this, parentStep.getAssociationList());
}
private void outboundEditPerformed(AjaxRequestTarget target){
MappingEditorDialog window = (MappingEditorDialog) get(ID_MODAL_OUTBOUND);
window.updateModel(target, new PropertyModel<MappingType>(getModel(), "outbound"), false);
window.show(target);
}
private void inboundEditPerformed(AjaxRequestTarget target, MappingType mapping){
MappingEditorDialog window = (MappingEditorDialog) get(ID_MODAL_INBOUND);
window.updateModel(target, mapping, true);
window.show(target);
}
}