/**
* 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.provisioning.ucf.impl.connid;
import java.io.File;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import org.identityconnectors.common.pooling.ObjectPoolConfiguration;
import org.identityconnectors.common.security.GuardedByteArray;
import org.identityconnectors.common.security.GuardedString;
import org.identityconnectors.framework.api.APIConfiguration;
import org.identityconnectors.framework.api.ConfigurationProperties;
import org.identityconnectors.framework.api.ConfigurationProperty;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.api.ResultsHandlerConfiguration;
import org.identityconnectors.framework.api.operations.APIOperation;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.crypto.EncryptionException;
import com.evolveum.midpoint.prism.crypto.Protector;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.ConnectorType;
import com.evolveum.prism.xml.ns._public.types_3.PolyStringType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedByteArrayType;
import com.evolveum.prism.xml.ns._public.types_3.ProtectedStringType;
/**
* @author semancik
*
*/
public class ConnIdConfigurationTransformer {
private static final Trace LOGGER = TraceManager.getTrace(ConnIdConfigurationTransformer.class);
private ConnectorType connectorType;
private ConnectorInfo cinfo;
private Protector protector;
public ConnIdConfigurationTransformer(ConnectorType connectorType, ConnectorInfo cinfo, Protector protector) {
super();
this.connectorType = connectorType;
this.cinfo = cinfo;
this.protector = protector;
}
/**
* Transforms midPoint XML configuration of the connector to the ICF
* configuration.
* <p/>
* The "configuration" part of the XML resource definition will be used.
* <p/>
* The provided ICF APIConfiguration will be modified, some values may be
* overwritten.
*
* @param apiConfig
* ICF connector configuration
* @param resourceType
* midPoint XML configuration
* @throws SchemaException
* @throws ConfigurationException
*/
public APIConfiguration transformConnectorConfiguration(PrismContainerValue configuration)
throws SchemaException, ConfigurationException {
APIConfiguration apiConfig = cinfo.createDefaultAPIConfiguration();
ConfigurationProperties configProps = apiConfig.getConfigurationProperties();
// The namespace of all the configuration properties specific to the
// connector instance will have a connector instance namespace. This
// namespace can be found in the resource definition.
String connectorConfNs = connectorType.getNamespace();
PrismContainer configurationPropertiesContainer = configuration
.findContainer(SchemaConstants.CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_QNAME);
if (configurationPropertiesContainer == null) {
// Also try this. This is an older way.
configurationPropertiesContainer = configuration.findContainer(new QName(connectorConfNs,
SchemaConstants.CONNECTOR_SCHEMA_CONFIGURATION_PROPERTIES_ELEMENT_LOCAL_NAME));
}
transformConnectorConfigurationProperties(configProps, configurationPropertiesContainer, connectorConfNs);
PrismContainer connectorPoolContainer = configuration.findContainer(new QName(
SchemaConstants.NS_ICF_CONFIGURATION,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_XML_ELEMENT_NAME));
ObjectPoolConfiguration connectorPoolConfiguration = apiConfig.getConnectorPoolConfiguration();
transformConnectorPoolConfiguration(connectorPoolConfiguration, connectorPoolContainer);
PrismProperty producerBufferSizeProperty = configuration.findProperty(new QName(
SchemaConstants.NS_ICF_CONFIGURATION,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_PRODUCER_BUFFER_SIZE_XML_ELEMENT_NAME));
if (producerBufferSizeProperty != null) {
apiConfig.setProducerBufferSize(parseInt(producerBufferSizeProperty));
}
PrismContainer connectorTimeoutsContainer = configuration.findContainer(new QName(
SchemaConstants.NS_ICF_CONFIGURATION,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME));
transformConnectorTimeoutsConfiguration(apiConfig, connectorTimeoutsContainer);
PrismContainer resultsHandlerConfigurationContainer = configuration.findContainer(new QName(
SchemaConstants.NS_ICF_CONFIGURATION,
ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME));
ResultsHandlerConfiguration resultsHandlerConfiguration = apiConfig.getResultsHandlerConfiguration();
transformResultsHandlerConfiguration(resultsHandlerConfiguration, resultsHandlerConfigurationContainer);
return apiConfig;
}
private void transformConnectorConfigurationProperties(ConfigurationProperties configProps,
PrismContainer<?> configurationPropertiesContainer, String connectorConfNs)
throws ConfigurationException, SchemaException {
if (configurationPropertiesContainer == null || configurationPropertiesContainer.getValue() == null) {
throw new SchemaException("No configuration properties container in " + connectorType);
}
int numConfingProperties = 0;
List<QName> wrongNamespaceProperties = new ArrayList<>();
for (PrismProperty prismProperty : configurationPropertiesContainer.getValue().getProperties()) {
QName propertyQName = prismProperty.getElementName();
// All the elements must be in a connector instance
// namespace.
if (propertyQName.getNamespaceURI() == null
|| !propertyQName.getNamespaceURI().equals(connectorConfNs)) {
LOGGER.warn("Found element with a wrong namespace ({}) in {}",
propertyQName.getNamespaceURI(), connectorType);
wrongNamespaceProperties.add(propertyQName);
} else {
numConfingProperties++;
// Local name of the element is the same as the name
// of ConnId configuration property
String propertyName = propertyQName.getLocalPart();
ConfigurationProperty property = configProps.getProperty(propertyName);
if (property == null) {
throw new ConfigurationException("Unknown configuration property "+propertyName);
}
Class<?> type = property.getType();
if (type.isArray()) {
Object[] connIdArray = convertToConnIdfArray(prismProperty, type.getComponentType());
if (connIdArray != null && connIdArray.length != 0) {
property.setValue(connIdArray);
}
} else {
Object connIdValue = convertToConnIdSingle(prismProperty, type);
if (connIdValue != null) {
property.setValue(connIdValue);
}
}
}
}
// empty configuration is OK e.g. when creating a new resource using wizard
if (numConfingProperties == 0 && !wrongNamespaceProperties.isEmpty()) {
throw new SchemaException("No configuration properties found. Wrong namespace? (expected: "
+ connectorConfNs + ", present e.g. " + wrongNamespaceProperties.get(0) + ")");
}
}
private void transformConnectorPoolConfiguration(ObjectPoolConfiguration connectorPoolConfiguration,
PrismContainer<?> connectorPoolContainer) throws SchemaException {
if (connectorPoolContainer == null || connectorPoolContainer.getValue() == null) {
return;
}
for (PrismProperty prismProperty : connectorPoolContainer.getValue().getProperties()) {
QName propertyQName = prismProperty.getElementName();
if (propertyQName.getNamespaceURI().equals(SchemaConstants.NS_ICF_CONFIGURATION)) {
String subelementName = propertyQName.getLocalPart();
if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_EVICTABLE_IDLE_TIME_MILLIS
.equals(subelementName)) {
connectorPoolConfiguration.setMinEvictableIdleTimeMillis(parseLong(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MIN_IDLE
.equals(subelementName)) {
connectorPoolConfiguration.setMinIdle(parseInt(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_IDLE
.equals(subelementName)) {
connectorPoolConfiguration.setMaxIdle(parseInt(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_OBJECTS
.equals(subelementName)) {
connectorPoolConfiguration.setMaxObjects(parseInt(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_MAX_WAIT
.equals(subelementName)) {
connectorPoolConfiguration.setMaxWait(parseLong(prismProperty));
} else {
throw new SchemaException(
"Unexpected element "
+ propertyQName
+ " in "
+ ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_XML_ELEMENT_NAME);
}
} else {
throw new SchemaException(
"Unexpected element "
+ propertyQName
+ " in "
+ ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_CONNECTOR_POOL_CONFIGURATION_XML_ELEMENT_NAME);
}
}
}
private void transformConnectorTimeoutsConfiguration(APIConfiguration apiConfig,
PrismContainer<?> connectorTimeoutsContainer) throws SchemaException {
if (connectorTimeoutsContainer == null || connectorTimeoutsContainer.getValue() == null) {
return;
}
for (PrismProperty prismProperty : connectorTimeoutsContainer.getValue().getProperties()) {
QName propertQName = prismProperty.getElementName();
if (SchemaConstants.NS_ICF_CONFIGURATION.equals(propertQName.getNamespaceURI())) {
String opName = propertQName.getLocalPart();
Class<? extends APIOperation> apiOpClass = ConnectorFactoryConnIdImpl.resolveApiOpClass(opName);
if (apiOpClass != null) {
apiConfig.setTimeout(apiOpClass, parseInt(prismProperty));
} else {
throw new SchemaException("Unknown operation name " + opName + " in "
+ ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_TIMEOUTS_XML_ELEMENT_NAME);
}
}
}
}
private void transformResultsHandlerConfiguration(ResultsHandlerConfiguration resultsHandlerConfiguration,
PrismContainer<?> resultsHandlerConfigurationContainer) throws SchemaException {
if (resultsHandlerConfigurationContainer == null || resultsHandlerConfigurationContainer.getValue() == null) {
return;
}
for (PrismProperty prismProperty : resultsHandlerConfigurationContainer.getValue().getProperties()) {
QName propertyQName = prismProperty.getElementName();
if (propertyQName.getNamespaceURI().equals(SchemaConstants.NS_ICF_CONFIGURATION)) {
String subelementName = propertyQName.getLocalPart();
if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_NORMALIZING_RESULTS_HANDLER
.equals(subelementName)) {
resultsHandlerConfiguration.setEnableNormalizingResultsHandler(parseBoolean(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_FILTERED_RESULTS_HANDLER
.equals(subelementName)) {
resultsHandlerConfiguration.setEnableFilteredResultsHandler(parseBoolean(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_FILTERED_RESULTS_HANDLER_IN_VALIDATION_MODE
.equals(subelementName)) {
resultsHandlerConfiguration.setFilteredResultsHandlerInValidationMode(parseBoolean(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_CASE_INSENSITIVE_HANDLER
.equals(subelementName)) {
resultsHandlerConfiguration.setEnableCaseInsensitiveFilter(parseBoolean(prismProperty));
} else if (ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ENABLE_ATTRIBUTES_TO_GET_SEARCH_RESULTS_HANDLER
.equals(subelementName)) {
resultsHandlerConfiguration.setEnableAttributesToGetSearchResultsHandler(parseBoolean(prismProperty));
} else {
throw new SchemaException(
"Unexpected element "
+ propertyQName
+ " in "
+ ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME);
}
} else {
throw new SchemaException(
"Unexpected element "
+ propertyQName
+ " in "
+ ConnectorFactoryConnIdImpl.CONNECTOR_SCHEMA_RESULTS_HANDLER_CONFIGURATION_ELEMENT_LOCAL_NAME);
}
}
}
private int parseInt(PrismProperty<?> prop) {
return prop.getRealValue(Integer.class);
}
private long parseLong(PrismProperty<?> prop) {
Object realValue = prop.getRealValue();
if (realValue instanceof Long) {
return (Long) realValue;
} else if (realValue instanceof Integer) {
return ((Integer) realValue);
} else {
throw new IllegalArgumentException("Cannot convert " + realValue.getClass() + " to long");
}
}
private boolean parseBoolean(PrismProperty<?> prop) {
return prop.getRealValue(Boolean.class);
}
private Object convertToConnIdSingle(PrismProperty<?> configProperty, Class<?> expectedType)
throws ConfigurationException {
if (configProperty == null) {
return null;
}
PrismPropertyValue<?> pval = configProperty.getValue();
return convertToIcf(pval, expectedType);
}
private Object[] convertToConnIdfArray(PrismProperty prismProperty, Class<?> componentType)
throws ConfigurationException {
List<PrismPropertyValue> values = prismProperty.getValues();
Object valuesArrary = Array.newInstance(componentType, values.size());
for (int j = 0; j < values.size(); ++j) {
Object icfValue = convertToIcf(values.get(j), componentType);
Array.set(valuesArrary, j, icfValue);
}
return (Object[]) valuesArrary;
}
private Object convertToIcf(PrismPropertyValue<?> pval, Class<?> expectedType) throws ConfigurationException {
Object midPointRealValue = pval.getValue();
if (expectedType.equals(GuardedString.class)) {
// Guarded string is a special ICF beast
// The value must be ProtectedStringType
if (midPointRealValue instanceof ProtectedStringType) {
ProtectedStringType ps = (ProtectedStringType) pval.getValue();
return ConnIdUtil.toGuardedString(ps, pval.getParent().getElementName().getLocalPart(), protector);
} else {
throw new ConfigurationException(
"Expected protected string as value of configuration property "
+ pval.getParent().getElementName().getLocalPart() + " but got "
+ midPointRealValue.getClass());
}
} else if (expectedType.equals(GuardedByteArray.class)) {
// Guarded string is a special ICF beast
// TODO
// return new GuardedByteArray(Base64.decodeBase64((ProtectedByteArrayType) pval.getValue()));
return new GuardedByteArray(((ProtectedByteArrayType) pval.getValue()).getClearBytes());
} else if (midPointRealValue instanceof PolyString) {
return ((PolyString)midPointRealValue).getOrig();
} else if (midPointRealValue instanceof PolyStringType) {
return ((PolyStringType)midPointRealValue).getOrig();
} else if (expectedType.equals(File.class) && midPointRealValue instanceof String) {
return new File((String)midPointRealValue);
} else if (expectedType.equals(String.class) && midPointRealValue instanceof ProtectedStringType) {
try {
return protector.decryptString((ProtectedStringType)midPointRealValue);
} catch (EncryptionException e) {
throw new ConfigurationException(e);
}
} else {
return midPointRealValue;
}
}
}