/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.syncope.console.pages;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.syncope.common.SyncopeClientException;
import org.apache.syncope.common.to.ConnBundleTO;
import org.apache.syncope.common.to.ConnInstanceTO;
import org.apache.syncope.common.to.ConnPoolConfTO;
import org.apache.syncope.common.types.ConnConfPropSchema;
import org.apache.syncope.common.types.ConnConfProperty;
import org.apache.syncope.common.types.ConnectorCapability;
import org.apache.syncope.console.commons.Constants;
import org.apache.syncope.console.markup.html.list.AltListView;
import org.apache.syncope.console.rest.ConnectorRestClient;
import org.apache.syncope.console.wicket.markup.html.form.AjaxCheckBoxPanel;
import org.apache.syncope.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
import org.apache.syncope.console.wicket.markup.html.form.AjaxPasswordFieldPanel;
import org.apache.syncope.console.wicket.markup.html.form.AjaxTextFieldPanel;
import org.apache.syncope.console.wicket.markup.html.form.FieldPanel;
import org.apache.syncope.console.wicket.markup.html.form.MultiFieldPanel;
import org.apache.syncope.console.wicket.markup.html.form.SpinnerFieldPanel;
import org.apache.wicket.PageReference;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.ajax.markup.html.form.AjaxButton;
import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
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.CheckBoxMultipleChoice;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.form.Form;
import org.apache.wicket.markup.html.form.PasswordTextField;
import org.apache.wicket.markup.html.list.ListItem;
import org.apache.wicket.markup.html.list.ListView;
import org.apache.wicket.model.CompoundPropertyModel;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.PropertyModel;
import org.apache.wicket.model.ResourceModel;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.apache.wicket.validation.validator.RangeValidator;
import org.springframework.util.ClassUtils;
/**
* Modal window with Connector form.
*/
public class ConnectorModalPage extends BaseModalPage {
private static final long serialVersionUID = -2025535531121434050L;
@SpringBean
private ConnectorRestClient restClient;
private final Map<String, Map<String, Map<String, ConnBundleTO>>> mapConnBundleTOs;
private final List<ConnectorCapability> selectedCapabilities;
private ConnBundleTO bundleTO;
private List<ConnConfProperty> properties;
private final WebMarkupContainer propertiesContainer;
public ConnectorModalPage(final PageReference pageRef, final ModalWindow window,
final ConnInstanceTO connInstanceTO) {
super();
// general data setup
selectedCapabilities = new ArrayList<ConnectorCapability>(connInstanceTO.getId() == 0
? EnumSet.noneOf(ConnectorCapability.class)
: connInstanceTO.getCapabilities());
mapConnBundleTOs = new HashMap<String, Map<String, Map<String, ConnBundleTO>>>();
for (ConnBundleTO connBundleTO : restClient.getAllBundles()) {
// by location
if (!mapConnBundleTOs.containsKey(connBundleTO.getLocation())) {
mapConnBundleTOs.put(connBundleTO.getLocation(), new HashMap<String, Map<String, ConnBundleTO>>());
}
final Map<String, Map<String, ConnBundleTO>> byLocation = mapConnBundleTOs.get(connBundleTO.getLocation());
// by name
if (!byLocation.containsKey(connBundleTO.getBundleName())) {
byLocation.put(connBundleTO.getBundleName(), new HashMap<String, ConnBundleTO>());
}
final Map<String, ConnBundleTO> byName = byLocation.get(connBundleTO.getBundleName());
// by version
if (!byName.containsKey(connBundleTO.getVersion())) {
byName.put(connBundleTO.getVersion(), connBundleTO);
}
}
bundleTO = getSelectedBundleTO(connInstanceTO);
properties = fillProperties(bundleTO, connInstanceTO);
// form - first tab
final Form<ConnInstanceTO> connectorForm = new Form<ConnInstanceTO>(FORM);
connectorForm.setModel(new CompoundPropertyModel<ConnInstanceTO>(connInstanceTO));
connectorForm.setOutputMarkupId(true);
add(connectorForm);
propertiesContainer = new WebMarkupContainer("container");
propertiesContainer.setOutputMarkupId(true);
connectorForm.add(propertiesContainer);
final Form<ConnInstanceTO> connectorPropForm = new Form<ConnInstanceTO>("connectorPropForm");
connectorPropForm.setModel(new CompoundPropertyModel<ConnInstanceTO>(connInstanceTO));
connectorPropForm.setOutputMarkupId(true);
propertiesContainer.add(connectorPropForm);
final AjaxTextFieldPanel displayName = new AjaxTextFieldPanel(
"displayName", "display name", new PropertyModel<String>(connInstanceTO, "displayName"));
displayName.setOutputMarkupId(true);
displayName.addRequiredLabel();
connectorForm.add(displayName);
final AjaxDropDownChoicePanel<String> location =
new AjaxDropDownChoicePanel<String>("location", "location",
new Model<String>(bundleTO == null ? null : bundleTO.getLocation()));
((DropDownChoice<String>) location.getField()).setNullValid(true);
location.setStyleSheet("long_dynamicsize");
location.setChoices(new ArrayList<String>(mapConnBundleTOs.keySet()));
location.setRequired(true);
location.addRequiredLabel();
location.setOutputMarkupId(true);
location.setEnabled(connInstanceTO.getId() == 0);
location.getField().setOutputMarkupId(true);
connectorForm.add(location);
final AjaxDropDownChoicePanel<String> connectorName =
new AjaxDropDownChoicePanel<String>("connectorName", "connectorName",
new Model<String>(bundleTO == null ? null : bundleTO.getBundleName()));
((DropDownChoice<String>) connectorName.getField()).setNullValid(true);
connectorName.setStyleSheet("long_dynamicsize");
connectorName.setChoices(bundleTO == null
? new ArrayList<String>()
: new ArrayList<String>(mapConnBundleTOs.get(connInstanceTO.getLocation()).keySet()));
connectorName.setRequired(true);
connectorName.addRequiredLabel();
connectorName.setEnabled(connInstanceTO.getLocation() != null);
connectorName.setOutputMarkupId(true);
connectorName.setEnabled(connInstanceTO.getId() == 0);
connectorName.getField().setOutputMarkupId(true);
connectorForm.add(connectorName);
final AjaxDropDownChoicePanel<String> version =
new AjaxDropDownChoicePanel<String>("version", "version",
new Model<String>(bundleTO == null ? null : bundleTO.getVersion()));
version.setStyleSheet("long_dynamicsize");
version.setChoices(bundleTO == null
? new ArrayList<String>()
: new ArrayList<String>(mapConnBundleTOs.get(connInstanceTO.getLocation()).
get(connInstanceTO.getBundleName()).keySet()));
version.setRequired(true);
version.addRequiredLabel();
version.setEnabled(connInstanceTO.getBundleName() != null);
version.setOutputMarkupId(true);
version.addRequiredLabel();
version.getField().setOutputMarkupId(true);
connectorForm.add(version);
final SpinnerFieldPanel<Integer> connRequestTimeout =
new SpinnerFieldPanel<Integer>("connRequestTimeout", "connRequestTimeout", Integer.class,
new PropertyModel<Integer>(connInstanceTO, "connRequestTimeout"), 0, null);
connRequestTimeout.getField().add(new RangeValidator<Integer>(0, Integer.MAX_VALUE));
connectorForm.add(connRequestTimeout);
if (connInstanceTO.getPoolConf() == null) {
connInstanceTO.setPoolConf(new ConnPoolConfTO());
}
final SpinnerFieldPanel<Integer> poolMaxObjects =
new SpinnerFieldPanel<Integer>("poolMaxObjects", "poolMaxObjects", Integer.class,
new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "maxObjects"), 0, null);
poolMaxObjects.getField().add(new RangeValidator<Integer>(0, Integer.MAX_VALUE));
connectorForm.add(poolMaxObjects);
final SpinnerFieldPanel<Integer> poolMinIdle =
new SpinnerFieldPanel<Integer>("poolMinIdle", "poolMinIdle", Integer.class,
new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "minIdle"), 0, null);
poolMinIdle.getField().add(new RangeValidator<Integer>(0, Integer.MAX_VALUE));
connectorForm.add(poolMinIdle);
final SpinnerFieldPanel<Integer> poolMaxIdle =
new SpinnerFieldPanel<Integer>("poolMaxIdle", "poolMaxIdle", Integer.class,
new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "maxIdle"), 0, null);
poolMaxIdle.getField().add(new RangeValidator<Integer>(0, Integer.MAX_VALUE));
connectorForm.add(poolMaxIdle);
final SpinnerFieldPanel<Long> poolMaxWait =
new SpinnerFieldPanel<Long>("poolMaxWait", "poolMaxWait", Long.class,
new PropertyModel<Long>(connInstanceTO.getPoolConf(), "maxWait"), 0L, null);
poolMaxWait.getField().add(new RangeValidator<Long>(0L, Long.MAX_VALUE));
connectorForm.add(poolMaxWait);
final SpinnerFieldPanel<Long> poolMinEvictableIdleTime =
new SpinnerFieldPanel<Long>("poolMinEvictableIdleTime", "poolMinEvictableIdleTime", Long.class,
new PropertyModel<Long>(connInstanceTO.getPoolConf(), "minEvictableIdleTimeMillis"),
0L, null);
poolMinEvictableIdleTime.getField().add(new RangeValidator<Long>(0L, Long.MAX_VALUE));
connectorForm.add(poolMinEvictableIdleTime);
// form - first tab - onchange()
location.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
((DropDownChoice<String>) location.getField()).setNullValid(false);
connInstanceTO.setLocation(location.getModelObject());
target.add(location);
connectorName.setChoices(new ArrayList<String>(
mapConnBundleTOs.get(location.getModelObject()).keySet()));
connectorName.setEnabled(true);
connectorName.getField().setModelValue(null);
target.add(connectorName);
version.setChoices(new ArrayList<String>());
version.getField().setModelValue(null);
version.setEnabled(false);
target.add(version);
properties.clear();
target.add(propertiesContainer);
}
});
connectorName.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
((DropDownChoice<String>) connectorName.getField()).setNullValid(false);
connInstanceTO.setBundleName(connectorName.getModelObject());
target.add(connectorName);
List<String> versions = new ArrayList<String>(
mapConnBundleTOs.get(location.getModelObject()).get(connectorName.getModelObject()).keySet());
version.setChoices(versions);
version.setEnabled(true);
if (versions.size() == 1) {
selectVersion(target, connInstanceTO, version, versions.get(0));
version.getField().setModelObject(versions.get(0));
} else {
version.getField().setModelValue(null);
properties.clear();
target.add(propertiesContainer);
}
target.add(version);
}
});
version.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
private static final long serialVersionUID = -1107858522700306810L;
@Override
protected void onUpdate(final AjaxRequestTarget target) {
selectVersion(target, connInstanceTO, version, version.getModelObject());
}
});
// form - second tab (properties)
final ListView<ConnConfProperty> connPropView = new AltListView<ConnConfProperty>(
"connectorProperties", new PropertyModel<List<ConnConfProperty>>(this, "properties")) {
private static final long serialVersionUID = 9101744072914090143L;
@Override
protected void populateItem(final ListItem<ConnConfProperty> item) {
final ConnConfProperty property = item.getModelObject();
final Label label = new Label("connPropAttrSchema",
StringUtils.isBlank(property.getSchema().getDisplayName())
? property.getSchema().getName()
: property.getSchema().getDisplayName());
item.add(label);
@SuppressWarnings("rawtypes")
final FieldPanel field;
boolean required = false;
boolean isArray = false;
if (property.getSchema().isConfidential()
|| Constants.GUARDED_STRING.equalsIgnoreCase(property.getSchema().getType())
|| Constants.GUARDED_BYTE_ARRAY.equalsIgnoreCase(property.getSchema().getType())) {
field = new AjaxPasswordFieldPanel("panel",
label.getDefaultModelObjectAsString(), new Model<String>());
((PasswordTextField) field.getField()).setResetPassword(false);
required = property.getSchema().isRequired();
} else {
Class<?> propertySchemaClass;
try {
propertySchemaClass =
ClassUtils.forName(property.getSchema().getType(), ClassUtils.getDefaultClassLoader());
if (ClassUtils.isPrimitiveOrWrapper(propertySchemaClass)) {
propertySchemaClass =
org.apache.commons.lang3.ClassUtils.primitiveToWrapper(propertySchemaClass);
}
} catch (Exception e) {
LOG.error("Error parsing attribute type", e);
propertySchemaClass = String.class;
}
if (ClassUtils.isAssignable(Number.class, propertySchemaClass)) {
@SuppressWarnings("unchecked")
final Class<Number> numberClass = (Class<Number>) propertySchemaClass;
field = new SpinnerFieldPanel<Number>("panel", label.getDefaultModelObjectAsString(),
numberClass, new Model<Number>(), null, null);
required = property.getSchema().isRequired();
} else if (ClassUtils.isAssignable(Boolean.class, propertySchemaClass)) {
field = new AjaxCheckBoxPanel("panel",
label.getDefaultModelObjectAsString(), new Model<Boolean>());
} else {
field = new AjaxTextFieldPanel("panel",
label.getDefaultModelObjectAsString(), new Model<String>());
required = property.getSchema().isRequired();
}
if (propertySchemaClass.isArray()) {
isArray = true;
}
}
field.setTitle(property.getSchema().getHelpMessage());
if (required) {
field.addRequiredLabel();
}
if (isArray) {
if (property.getValues().isEmpty()) {
property.getValues().add(null);
}
@SuppressWarnings("unchecked")
final MultiFieldPanel<String> multiFieldPanel = new MultiFieldPanel<String>(
"panel", new PropertyModel<List<String>>(property, "values"), field);
item.add(multiFieldPanel);
} else {
setNewFieldModel(field, property.getValues());
item.add(field);
}
final AjaxCheckBoxPanel overridable = new AjaxCheckBoxPanel("connPropAttrOverridable",
"connPropAttrOverridable", new PropertyModel<Boolean>(property, "overridable"));
item.add(overridable);
connInstanceTO.getConfiguration().add(property);
}
};
connPropView.setOutputMarkupId(true);
connectorPropForm.add(connPropView);
final AjaxLink<String> check = new IndicatingAjaxLink<String>("check", new ResourceModel("check")) {
private static final long serialVersionUID = -7978723352517770644L;
@Override
public void onClick(final AjaxRequestTarget target) {
connInstanceTO.setBundleName(bundleTO.getBundleName());
connInstanceTO.setVersion(bundleTO.getVersion());
connInstanceTO.setConnectorName(bundleTO.getConnectorName());
if (restClient.check(connInstanceTO)) {
info(getString("success_connection"));
} else {
error(getString("error_connection"));
}
feedbackPanel.refresh(target);
}
};
connectorPropForm.add(check);
// form - third tab (capabilities)
final IModel<List<ConnectorCapability>> capabilities =
new LoadableDetachableModel<List<ConnectorCapability>>() {
private static final long serialVersionUID = 5275935387613157437L;
@Override
protected List<ConnectorCapability> load() {
return Arrays.asList(ConnectorCapability.values());
}
};
CheckBoxMultipleChoice<ConnectorCapability> capabilitiesPalette =
new CheckBoxMultipleChoice<ConnectorCapability>("capabilitiesPalette",
new PropertyModel<List<ConnectorCapability>>(this, "selectedCapabilities"), capabilities);
connectorForm.add(capabilitiesPalette);
// form - submit / cancel buttons
final AjaxButton submit = new IndicatingAjaxButton(APPLY, new Model<String>(getString(SUBMIT))) {
private static final long serialVersionUID = -958724007591692537L;
@Override
protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
final ConnInstanceTO conn = (ConnInstanceTO) form.getModelObject();
conn.setConnectorName(bundleTO.getConnectorName());
conn.setBundleName(bundleTO.getBundleName());
conn.setVersion(bundleTO.getVersion());
conn.getConfiguration().clear();
conn.getConfiguration().addAll(connPropView.getModelObject());
// Set the model object's capabilities to capabilitiesPalette's converted Set
conn.getCapabilities().addAll(selectedCapabilities.isEmpty()
? EnumSet.noneOf(ConnectorCapability.class)
: EnumSet.copyOf(selectedCapabilities));
// Reset pool configuration if all fields are null
if (conn.getPoolConf() != null
&& conn.getPoolConf().getMaxIdle() == null
&& conn.getPoolConf().getMaxObjects() == null
&& conn.getPoolConf().getMaxWait() == null
&& conn.getPoolConf().getMinEvictableIdleTimeMillis() == null
&& conn.getPoolConf().getMinIdle() == null) {
conn.setPoolConf(null);
}
try {
if (connInstanceTO.getId() == 0) {
restClient.create(conn);
} else {
restClient.update(conn);
}
((Resources) pageRef.getPage()).setModalResult(true);
window.close(target);
} catch (SyncopeClientException e) {
error(getString(Constants.ERROR) + ": " + e.getMessage());
feedbackPanel.refresh(target);
((Resources) pageRef.getPage()).setModalResult(false);
LOG.error("While creating or updating connector {}", conn, e);
}
}
@Override
protected void onError(final AjaxRequestTarget target, final Form<?> form) {
feedbackPanel.refresh(target);
}
};
String roles = connInstanceTO.getId() == 0
? xmlRolesReader.getAllAllowedRoles("Connectors", "create")
: xmlRolesReader.getAllAllowedRoles("Connectors", "update");
MetaDataRoleAuthorizationStrategy.authorize(submit, ENABLE, roles);
connectorForm.add(submit);
final IndicatingAjaxButton cancel = new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) {
private static final long serialVersionUID = -958724007591692537L;
@Override
protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
window.close(target);
}
};
cancel.setDefaultFormProcessing(false);
connectorForm.add(cancel);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private void setNewFieldModel(final FieldPanel field, final List<Object> values) {
field.setNewModel(values);
}
private ConnBundleTO getSelectedBundleTO(final ConnInstanceTO connInstanceTO) {
ConnBundleTO result = null;
if (connInstanceTO != null
&& StringUtils.isNotBlank(connInstanceTO.getLocation())
&& StringUtils.isNotBlank(connInstanceTO.getBundleName())
&& StringUtils.isNotBlank(connInstanceTO.getVersion())
&& mapConnBundleTOs.containsKey(connInstanceTO.getLocation())) {
Map<String, Map<String, ConnBundleTO>> byLocation = mapConnBundleTOs.get(connInstanceTO.getLocation());
if (byLocation.containsKey(connInstanceTO.getBundleName())) {
Map<String, ConnBundleTO> byName = byLocation.get(connInstanceTO.getBundleName());
if (byName.containsKey(connInstanceTO.getVersion())) {
result = byName.get(connInstanceTO.getVersion());
}
}
}
return result;
}
private List<ConnConfProperty> fillProperties(final ConnBundleTO bundleTO, final ConnInstanceTO connInstanceTO) {
final List<ConnConfProperty> props = new ArrayList<ConnConfProperty>();
if (bundleTO != null) {
for (ConnConfPropSchema key : bundleTO.getProperties()) {
final ConnConfProperty property = new ConnConfProperty();
property.setSchema(key);
if (connInstanceTO.getId() != 0
&& connInstanceTO.getConfigurationMap().containsKey(key.getName())
&& connInstanceTO.getConfigurationMap().get(key.getName()).getValues() != null) {
property.getValues().addAll(connInstanceTO.getConfigurationMap().get(key.getName()).getValues());
property.setOverridable(connInstanceTO.getConfigurationMap().get(key.getName()).isOverridable());
}
if (property.getValues().isEmpty() && !key.getDefaultValues().isEmpty()) {
property.getValues().addAll(key.getDefaultValues());
}
props.add(property);
}
}
// re-order properties (implements Comparable)
Collections.sort(props);
return props;
}
private void selectVersion(final AjaxRequestTarget target, final ConnInstanceTO connInstanceTO,
final AjaxDropDownChoicePanel<String> version, final String versionValue) {
connInstanceTO.setVersion(versionValue);
target.add(version);
bundleTO = getSelectedBundleTO(connInstanceTO);
properties = fillProperties(bundleTO, connInstanceTO);
target.add(propertiesContainer);
}
public List<ConnConfProperty> getProperties() {
return properties;
}
}