blob: 8a1f7d16ba94d012a530e0d5e4f6e3e6f1fd9407 [file] [log] [blame]
/*
* 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.core.provisioning.java;
import java.io.File;
import java.net.URI;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.syncope.common.lib.types.ConnectorCapability;
import org.apache.syncope.core.persistence.api.entity.ConnInstance;
import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
import org.apache.syncope.core.provisioning.api.utils.ConnPoolConfUtils;
import org.apache.syncope.core.provisioning.api.Connector;
import org.apache.syncope.core.provisioning.api.TimeoutException;
import org.apache.syncope.core.provisioning.api.pushpull.ReconFilterBuilder;
import org.apache.syncope.core.spring.ApplicationContextProvider;
import org.identityconnectors.common.CollectionUtil;
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.ConnectorFacade;
import org.identityconnectors.framework.api.ConnectorFacadeFactory;
import org.identityconnectors.framework.api.ConnectorInfo;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.ConnectorObject;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.ObjectClassInfo;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationOptionsBuilder;
import org.identityconnectors.framework.common.objects.SearchResult;
import org.identityconnectors.framework.common.objects.SyncResultsHandler;
import org.identityconnectors.framework.common.objects.SyncToken;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.spi.SearchResultsHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ClassUtils;
public class ConnectorFacadeProxy implements Connector {
private static final Logger LOG = LoggerFactory.getLogger(ConnectorFacadeProxy.class);
private static final Integer DEFAULT_PAGE_SIZE = 100;
/**
* Connector facade wrapped instance.
*/
private final ConnectorFacade connector;
/**
* Active connector instance.
*/
private final ConnInstance connInstance;
private final AsyncConnectorFacade asyncFacade;
/**
* Use the passed connector instance to build a ConnectorFacade that will be used to make all wrapped calls.
*
* @param connInstance the connector instance
* @param asyncFacade the async connectot facade
* @see ConnectorInfo
* @see APIConfiguration
* @see ConfigurationProperties
* @see ConnectorFacade
*/
public ConnectorFacadeProxy(final ConnInstance connInstance, final AsyncConnectorFacade asyncFacade) {
this.connInstance = connInstance;
this.asyncFacade = asyncFacade;
ConnIdBundleManager connIdBundleManager =
ApplicationContextProvider.getBeanFactory().getBean(ConnIdBundleManager.class);
ConnectorInfo info = connIdBundleManager.getConnectorInfo(connInstance).getRight();
// create default configuration
APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
if (connInstance.getDisplayName() != null) {
apiConfig.setInstanceName(connInstance.getDisplayName());
}
// enable filtered results handler in validation mode
apiConfig.getResultsHandlerConfiguration().setFilteredResultsHandlerInValidationMode(true);
// set connector configuration according to conninstance's
ConfigurationProperties properties = apiConfig.getConfigurationProperties();
connInstance.getConf().stream().
filter(property -> !CollectionUtil.isEmpty(property.getValues())).
forEach(property -> properties.setPropertyValue(
property.getSchema().getName(),
getPropertyValue(property.getSchema().getType(), property.getValues())));
// set pooling configuration (if supported) according to conninstance's
if (connInstance.getPoolConf() != null) {
if (apiConfig.isConnectorPoolingSupported()) {
ConnPoolConfUtils.updateObjectPoolConfiguration(
apiConfig.getConnectorPoolConfiguration(), connInstance.getPoolConf());
} else {
LOG.warn("Connector pooling not supported for {}", info);
}
}
// gets new connector, with the given configuration
connector = ConnectorFacadeFactory.getInstance().newInstance(apiConfig);
// make sure we have set up the Configuration properly
connector.validate();
}
@Override
public Uid authenticate(final String username, final String password, final OperationOptions options) {
Uid result = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.AUTHENTICATE)) {
Future<Uid> future = asyncFacade.authenticate(
connector, username, new GuardedString(password.toCharArray()), options);
try {
result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
} else {
LOG.info("Authenticate was attempted, although the connector only has these capabilities: {}. No action.",
connInstance.getCapabilities());
}
return result;
}
@Override
public Uid create(
final ObjectClass objectClass,
final Set<Attribute> attrs,
final OperationOptions options,
final AtomicReference<Boolean> propagationAttempted) {
Uid result = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) {
propagationAttempted.set(true);
Future<Uid> future = asyncFacade.create(connector, objectClass, attrs, options);
try {
result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
} else {
LOG.info("Create was attempted, although the connector only has these capabilities: {}. No action.",
connInstance.getCapabilities());
}
return result;
}
@Override
public Uid update(
final ObjectClass objectClass,
final Uid uid,
final Set<Attribute> attrs,
final OperationOptions options,
final AtomicReference<Boolean> propagationAttempted) {
Uid result = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
propagationAttempted.set(true);
Future<Uid> future = asyncFacade.update(connector, objectClass, uid, attrs, options);
try {
result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
} else {
LOG.info("Update for {} was attempted, although the "
+ "connector only has these capabilities: {}. No action.",
uid.getUidValue(), connInstance.getCapabilities());
}
return result;
}
@Override
public void delete(
final ObjectClass objectClass,
final Uid uid,
final OperationOptions options,
final AtomicReference<Boolean> propagationAttempted) {
if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) {
propagationAttempted.set(true);
Future<Uid> future = asyncFacade.delete(connector, objectClass, uid, options);
try {
future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
} else {
LOG.info("Delete for {} was attempted, although the connector only has these capabilities: {}. No action.",
uid.getUidValue(), connInstance.getCapabilities());
}
}
@Override
public void sync(final ObjectClass objectClass, final SyncToken token, final SyncResultsHandler handler,
final OperationOptions options) {
if (connInstance.getCapabilities().contains(ConnectorCapability.SYNC)) {
connector.sync(objectClass, token, handler, options);
} else {
LOG.info("Sync was attempted, although the connector only has these capabilities: {}. No action.",
connInstance.getCapabilities());
}
}
@Override
public SyncToken getLatestSyncToken(final ObjectClass objectClass) {
SyncToken result = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.SYNC)) {
Future<SyncToken> future = asyncFacade.getLatestSyncToken(connector, objectClass);
try {
result = future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
} else {
LOG.info("getLatestSyncToken was attempted, although the "
+ "connector only has these capabilities: {}. No action.", connInstance.getCapabilities());
}
return result;
}
@Override
public void fullReconciliation(
final ObjectClass objectClass,
final SyncResultsHandler handler,
final OperationOptions options) {
Connector.super.fullReconciliation(objectClass, handler, options);
}
@Override
public void filteredReconciliation(
final ObjectClass objectClass,
final ReconFilterBuilder filterBuilder,
final SyncResultsHandler handler,
final OperationOptions options) {
Connector.super.filteredReconciliation(objectClass, filterBuilder, handler, options);
}
@Override
public Set<ObjectClassInfo> getObjectClassInfo() {
Future<Set<ObjectClassInfo>> future = asyncFacade.getObjectClassInfo(connector);
try {
return future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
@Override
public void validate() {
Future<String> future = asyncFacade.test(connector);
try {
future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
@Override
public void test() {
Future<String> future = asyncFacade.test(connector);
try {
future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
@Override
public ConnectorObject getObject(
final ObjectClass objectClass,
final Attribute connObjectKey,
final boolean ignoreCaseMatch,
final OperationOptions options) {
Future<ConnectorObject> future = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
future = asyncFacade.getObject(connector, objectClass, connObjectKey, ignoreCaseMatch, options);
} else {
LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.",
connInstance.getCapabilities());
}
try {
return future == null ? null : future.get(connInstance.getConnRequestTimeout(), TimeUnit.SECONDS);
} catch (java.util.concurrent.TimeoutException e) {
future.cancel(true);
throw new TimeoutException("Request timeout");
} catch (Exception e) {
LOG.error("Connector request execution failure", e);
if (e.getCause() instanceof RuntimeException) {
throw (RuntimeException) e.getCause();
} else {
throw new RuntimeException(e.getCause());
}
}
}
@Override
public SearchResult search(
final ObjectClass objectClass,
final Filter filter,
final SearchResultsHandler handler,
final OperationOptions options) {
SearchResult result = null;
if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
if (options.getPageSize() == null && options.getPagedResultsCookie() == null) {
OperationOptionsBuilder builder = new OperationOptionsBuilder(options).
setPageSize(DEFAULT_PAGE_SIZE).setPagedResultsOffset(-1);
final String[] cookies = new String[] { null };
do {
if (cookies[0] != null) {
builder.setPagedResultsCookie(cookies[0]);
}
result = connector.search(objectClass, filter, new SearchResultsHandler() {
@Override
public void handleResult(final SearchResult result) {
handler.handleResult(result);
cookies[0] = result.getPagedResultsCookie();
}
@Override
public boolean handle(final ConnectorObject connectorObject) {
return handler.handle(connectorObject);
}
}, builder.build());
} while (cookies[0] != null);
} else {
result = connector.search(objectClass, filter, handler, options);
}
} else {
LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.",
connInstance.getCapabilities());
}
return result;
}
@Override
public void dispose() {
connector.dispose();
}
@Override
public ConnInstance getConnInstance() {
return connInstance;
}
private static Object getPropertyValue(final String propType, final List<?> values) {
Object value = null;
try {
Class<?> propertySchemaClass = ClassUtils.forName(propType, ClassUtils.getDefaultClassLoader());
if (GuardedString.class.equals(propertySchemaClass)) {
value = new GuardedString(values.get(0).toString().toCharArray());
} else if (GuardedByteArray.class.equals(propertySchemaClass)) {
value = new GuardedByteArray((byte[]) values.get(0));
} else if (Character.class.equals(propertySchemaClass) || Character.TYPE.equals(propertySchemaClass)) {
value = values.get(0) == null || values.get(0).toString().isEmpty()
? null : values.get(0).toString().charAt(0);
} else if (Integer.class.equals(propertySchemaClass) || Integer.TYPE.equals(propertySchemaClass)) {
value = Integer.parseInt(values.get(0).toString());
} else if (Long.class.equals(propertySchemaClass) || Long.TYPE.equals(propertySchemaClass)) {
value = Long.parseLong(values.get(0).toString());
} else if (Float.class.equals(propertySchemaClass) || Float.TYPE.equals(propertySchemaClass)) {
value = Float.parseFloat(values.get(0).toString());
} else if (Double.class.equals(propertySchemaClass) || Double.TYPE.equals(propertySchemaClass)) {
value = Double.parseDouble(values.get(0).toString());
} else if (Boolean.class.equals(propertySchemaClass) || Boolean.TYPE.equals(propertySchemaClass)) {
value = Boolean.parseBoolean(values.get(0).toString());
} else if (URI.class.equals(propertySchemaClass)) {
value = URI.create(values.get(0).toString());
} else if (File.class.equals(propertySchemaClass)) {
value = new File(values.get(0).toString());
} else if (String[].class.equals(propertySchemaClass)) {
value = values.toArray(new String[] {});
} else {
value = values.get(0) == null ? null : values.get(0).toString();
}
} catch (Exception e) {
LOG.error("Invalid ConnConfProperty specified: {} {}", propType, values, e);
}
return value;
}
@Override
public String toString() {
return "ConnectorFacadeProxy{"
+ "connector=" + connector + '\n' + "capabitilies=" + connInstance.getCapabilities() + '}';
}
}