You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/11/10 19:04:03 UTC

[4/4] syncope git commit: [SYNCOPE-729] Merge from 1_2_X

[SYNCOPE-729] Merge from 1_2_X


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/9da95172
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/9da95172
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/9da95172

Branch: refs/heads/master
Commit: 9da95172a5eadfe9ffcd021ccc4ba8b00a0a9ace
Parents: e8eceae c6e0081
Author: Francesco Chicchiriccò <il...@apache.org>
Authored: Tue Nov 10 19:03:48 2015 +0100
Committer: Francesco Chicchiriccò <il...@apache.org>
Committed: Tue Nov 10 19:03:48 2015 +0100

----------------------------------------------------------------------
 .../core/provisioning/api/Connector.java        | 16 ----------
 .../provisioning/java/ConnectorFacadeProxy.java | 33 +-------------------
 .../AbstractPropagationTaskExecutor.java        |  5 ++-
 3 files changed, 3 insertions(+), 51 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/9da95172/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
----------------------------------------------------------------------
diff --cc core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
index 2a3a0a1,0000000..c93c2c4
mode 100644,000000..100644
--- a/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
+++ b/core/provisioning-api/src/main/java/org/apache/syncope/core/provisioning/api/Connector.java
@@@ -1,211 -1,0 +1,195 @@@
 +/*
 + * 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.api;
 +
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Set;
- import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 +import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 +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.ResultsHandler;
 +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;
 +
 +/**
 + * Entry point for making requests on underlying connector bundles.
 + */
 +public interface Connector {
 +
 +    /**
 +     * Authenticate user on a connector instance.
 +     *
 +     * @param username the name based credential for authentication
 +     * @param password the password based credential for authentication
 +     * @param options ConnId's OperationOptions
 +     * @return Uid of the account that was used to authenticate
 +     */
 +    Uid authenticate(String username, String password, OperationOptions options);
 +
 +    /**
 +     * Create user / group on a connector instance.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param attrs attributes for creation
 +     * @param options ConnId's OperationOptions
 +     * @param propagationAttempted if creation is actually performed (based on connector instance's capabilities)
 +     * @return Uid for created object
 +     */
 +    Uid create(
 +            ObjectClass objectClass,
 +            Set<Attribute> attrs,
 +            OperationOptions options,
 +            Boolean[] propagationAttempted);
 +
 +    /**
 +     * Update user / group on a connector instance.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param uid user to be updated
 +     * @param attrs attributes for update
 +     * @param options ConnId's OperationOptions
 +     * @param propagationAttempted if creation is actually performed (based on connector instance's capabilities)
 +     * @return Uid for updated object
 +     */
 +    Uid update(
 +            ObjectClass objectClass,
 +            Uid uid,
 +            Set<Attribute> attrs,
 +            OperationOptions options,
 +            Boolean[] propagationAttempted);
 +
 +    /**
 +     * Delete user / group on a connector instance.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param uid user to be deleted
 +     * @param options ConnId's OperationOptions
 +     * @param propagationAttempted if deletion is actually performed (based on connector instance's capabilities)
 +     */
 +    void delete(
 +            ObjectClass objectClass,
 +            Uid uid,
 +            OperationOptions options,
 +            Boolean[] propagationAttempted);
 +
 +    /**
 +     * Fetches all remote objects (for use during full reconciliation).
 +     *
 +     * @param objectClass ConnId's object class.
 +     * @param handler to be used to handle deltas.
 +     * @param options ConnId's OperationOptions.
 +     */
 +    void getAllObjects(ObjectClass objectClass, SyncResultsHandler handler, OperationOptions options);
 +
 +    /**
 +     * Sync remote objects from a connector instance.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param token to be passed to the underlying connector
 +     * @param handler to be used to handle deltas
 +     * @param options ConnId's OperationOptions
 +     */
 +    void sync(ObjectClass objectClass, SyncToken token, SyncResultsHandler handler, OperationOptions options);
 +
 +    /**
 +     * Read latest sync token from a connector instance.
 +     *
 +     * @param objectClass ConnId's object class.
 +     * @return latest sync token
 +     */
 +    SyncToken getLatestSyncToken(ObjectClass objectClass);
 +
 +    /**
 +     * Get remote object.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param uid ConnId's Uid
 +     * @param options ConnId's OperationOptions
 +     * @return ConnId's connector object for given uid
 +     */
 +    ConnectorObject getObject(ObjectClass objectClass, Uid uid, OperationOptions options);
 +
 +    /**
-      * Get remote object with check for intended operation to perform on external resource.
-      *
-      * @param operationType resource operation type
-      * @param objectClass ConnId's object class
-      * @param uid ConnId's Uid
-      * @param options ConnId's OperationOptions
-      * @return ConnId's connector object for given uid
-      */
-     ConnectorObject getObject(
-             ResourceOperation operationType,
-             ObjectClass objectClass,
-             Uid uid,
-             OperationOptions options);
- 
-     /**
 +     * Search for remote objects.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param filter search filter
 +     * @param handler class responsible for working with the objects returned from the search; may be null.
 +     * @param options ConnId's OperationOptions
 +     */
 +    void search(
 +            ObjectClass objectClass,
 +            Filter filter,
 +            ResultsHandler handler,
 +            OperationOptions options);
 +
 +    /**
 +     * Search for remote objects.
 +     *
 +     * @param objectClass ConnId's object class
 +     * @param filter search filter
 +     * @param handler class responsible for working with the objects returned from the search; may be null.
 +     * @param pageSize requested page results page size
 +     * @param pagedResultsCookie an opaque cookie which is used by the connector to track its position in the set of
 +     * query results
 +     * @param orderBy the sort keys which should be used for ordering the {@link ConnectorObject} returned by
 +     * search request
 +     * @param mapItems mapping items
 +     */
 +    void search(
 +            ObjectClass objectClass,
 +            Filter filter,
 +            ResultsHandler handler,
 +            int pageSize,
 +            String pagedResultsCookie,
 +            List<OrderByClause> orderBy,
 +            Iterator<? extends MappingItem> mapItems);
 +
 +    /**
 +     * Builds metadata description of ConnId {@link ObjectClass}.
 +     *
 +     * @return metadata description of ConnId ObjectClass
 +     */
 +    Set<ObjectClassInfo> getObjectClassInfo();
 +
 +    /**
 +     * Validate a connector instance.
 +     */
 +    void validate();
 +
 +    /**
 +     * Check connection to resource.
 +     */
 +    void test();
 +
 +    /**
 +     * Getter for active connector instance.
 +     *
 +     * @return active connector instance.
 +     */
 +    ConnInstance getConnInstance();
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9da95172/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
index d39c9e9,0000000..86add58
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/ConnectorFacadeProxy.java
@@@ -1,556 -1,0 +1,525 @@@
 +/*
 + * 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.ArrayList;
 +import java.util.Iterator;
 +import java.util.List;
 +import java.util.Set;
 +import java.util.concurrent.Future;
 +import java.util.concurrent.TimeUnit;
 +import org.apache.commons.collections4.CollectionUtils;
 +import org.apache.commons.collections4.Transformer;
 +import org.apache.syncope.common.lib.types.ConnConfProperty;
 +import org.apache.syncope.common.lib.types.ConnectorCapability;
- import org.apache.syncope.common.lib.types.ResourceOperation;
 +import org.apache.syncope.core.misc.utils.MappingUtils;
 +import org.apache.syncope.core.persistence.api.entity.ConnInstance;
 +import org.apache.syncope.core.provisioning.api.ConnIdBundleManager;
 +import org.apache.syncope.core.provisioning.api.ConnPoolConfUtils;
 +import org.apache.syncope.core.provisioning.api.Connector;
 +import org.apache.syncope.core.provisioning.api.TimeoutException;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.persistence.api.dao.search.OrderByClause;
 +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 +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.ResultsHandler;
 +import org.identityconnectors.framework.common.objects.SearchResult;
 +import org.identityconnectors.framework.common.objects.SortKey;
 +import org.identityconnectors.framework.common.objects.SyncDeltaBuilder;
 +import org.identityconnectors.framework.common.objects.SyncDeltaType;
 +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.beans.factory.annotation.Autowired;
 +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;
 +
 +    @Autowired
 +    private 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
 +     * @see ConnectorInfo
 +     * @see APIConfiguration
 +     * @see ConfigurationProperties
 +     * @see ConnectorFacade
 +     */
 +    public ConnectorFacadeProxy(final ConnInstance connInstance) {
 +        this.connInstance = connInstance;
 +
 +        ConnIdBundleManager connIdBundleManager = ApplicationContextProvider.getBeanFactory().getBean(
 +                ConnIdBundleManager.class);
 +        ConnectorInfo info = connIdBundleManager.getConnectorInfo(connInstance);
 +
 +        // create default configuration
 +        APIConfiguration apiConfig = info.createDefaultAPIConfiguration();
 +        // enable filtered results handler in validation mode
 +        apiConfig.getResultsHandlerConfiguration().setFilteredResultsHandlerInValidationMode(true);
 +
 +        // set connector configuration according to conninstance's
 +        ConfigurationProperties properties = apiConfig.getConfigurationProperties();
 +        for (ConnConfProperty property : connInstance.getConf()) {
 +            if (property.getValues() != null && !property.getValues().isEmpty()) {
 +                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 IllegalArgumentException(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 Boolean[] propagationAttempted) {
 +
 +        Uid result = null;
 +
 +        if (connInstance.getCapabilities().contains(ConnectorCapability.CREATE)) {
 +            propagationAttempted[0] = 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 IllegalArgumentException(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 Boolean[] propagationAttempted) {
 +
 +        Uid result = null;
 +
 +        if (connInstance.getCapabilities().contains(ConnectorCapability.UPDATE)) {
 +            propagationAttempted[0] = 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 IllegalArgumentException(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 Boolean[] propagationAttempted) {
 +
 +        if (connInstance.getCapabilities().contains(ConnectorCapability.DELETE)) {
 +            propagationAttempted[0] = 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 IllegalArgumentException(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 IllegalArgumentException(e.getCause());
 +                }
 +            }
 +        } else {
 +            LOG.info("getLatestSyncToken was attempted, although the "
 +                    + "connector only has these capabilities: {}. No action.", connInstance.getCapabilities());
 +        }
 +
 +        return result;
 +    }
 +
 +    @Override
 +    public ConnectorObject getObject(final ObjectClass objectClass, final Uid uid, final OperationOptions options) {
-         return getObject(null, objectClass, uid, options);
-     }
- 
-     @Override
-     public ConnectorObject getObject(
-             final ResourceOperation operationType,
-             final ObjectClass objectClass,
-             final Uid uid,
-             final OperationOptions options) {
- 
-         boolean hasCapablities = false;
++        Future<ConnectorObject> future = null;
 +
 +        if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
-             if (operationType == null) {
-                 hasCapablities = true;
-             } else {
-                 switch (operationType) {
-                     case CREATE:
-                         hasCapablities = connInstance.getCapabilities().contains(ConnectorCapability.CREATE);
-                         break;
- 
-                     case UPDATE:
-                         hasCapablities = connInstance.getCapabilities().contains(ConnectorCapability.UPDATE);
-                         break;
- 
-                     default:
-                         hasCapablities = true;
-                 }
-             }
-         }
- 
-         Future<ConnectorObject> future = null;
-         if (hasCapablities) {
 +            future = asyncFacade.getObject(connector, objectClass, uid, 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) {
 +            if (future != null) {
 +                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 IllegalArgumentException(e.getCause());
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public void getAllObjects(
 +            final ObjectClass objectClass, final SyncResultsHandler handler, final OperationOptions options) {
 +
 +        search(objectClass, null, new ResultsHandler() {
 +
 +            @Override
 +            public boolean handle(final ConnectorObject obj) {
 +                return handler.handle(new SyncDeltaBuilder().
 +                        setObject(obj).
 +                        setUid(obj.getUid()).
 +                        setDeltaType(SyncDeltaType.CREATE_OR_UPDATE).
 +                        setToken(new SyncToken("")).
 +                        build());
 +            }
 +        }, 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 IllegalArgumentException(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 IllegalArgumentException(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 IllegalArgumentException(e.getCause());
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public void search(
 +            final ObjectClass objectClass,
 +            final Filter filter,
 +            final ResultsHandler handler,
 +            final OperationOptions options) {
 +
 +        if (connInstance.getCapabilities().contains(ConnectorCapability.SEARCH)) {
 +            if (options.getPageSize() == null && options.getPagedResultsCookie() == null) {
 +                OperationOptionsBuilder builder = new OperationOptionsBuilder(options);
 +                builder.setPageSize(DEFAULT_PAGE_SIZE);
 +
 +                final String[] cookies = new String[] { null };
 +                do {
 +                    if (cookies[0] != null) {
 +                        builder.setPagedResultsCookie(cookies[0]);
 +                    }
 +
 +                    connector.search(objectClass, filter, new SearchResultsHandler() {
 +
 +                        @Override
 +                        public void handleResult(final SearchResult result) {
 +                            if (handler instanceof SearchResultsHandler) {
 +                                SearchResultsHandler.class.cast(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 {
 +                connector.search(objectClass, filter, handler, options);
 +            }
 +        } else {
 +            LOG.info("Search was attempted, although the connector only has these capabilities: {}. No action.",
 +                    connInstance.getCapabilities());
 +        }
 +    }
 +
 +    @Override
 +    public void search(
 +            final ObjectClass objectClass,
 +            final Filter filter,
 +            final ResultsHandler handler,
 +            final int pageSize,
 +            final String pagedResultsCookie,
 +            final List<OrderByClause> orderBy,
 +            final Iterator<? extends MappingItem> mapItems) {
 +
 +        OperationOptionsBuilder builder = new OperationOptionsBuilder().setPageSize(pageSize);
 +        if (pagedResultsCookie != null) {
 +            builder.setPagedResultsCookie(pagedResultsCookie);
 +        }
 +        builder.setSortKeys(CollectionUtils.collect(orderBy, new Transformer<OrderByClause, SortKey>() {
 +
 +            @Override
 +            public SortKey transform(final OrderByClause clause) {
 +                return new SortKey(clause.getField(), clause.getDirection() == OrderByClause.Direction.ASC);
 +            }
 +        }, new ArrayList<SortKey>(orderBy.size())));
 +
 +        builder.setAttributesToGet(MappingUtils.buildOperationOptions(mapItems).getAttributesToGet());
 +
 +        search(objectClass, filter, handler, builder.build());
 +    }
 +
 +    @Override
 +    public ConnInstance getConnInstance() {
 +        return connInstance;
 +    }
 +
 +    private 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() + '}';
 +    }
 +}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9da95172/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
----------------------------------------------------------------------
diff --cc core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
index 37dc851,0000000..9367edc
mode 100644,000000..100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/propagation/AbstractPropagationTaskExecutor.java
@@@ -1,604 -1,0 +1,603 @@@
 +/*
 + * 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.propagation;
 +
 +import java.util.ArrayList;
 +import java.util.Collection;
 +import java.util.Collections;
 +import java.util.Date;
 +import java.util.HashMap;
 +import java.util.HashSet;
 +import java.util.List;
 +import java.util.Map;
 +import java.util.Set;
 +import org.apache.commons.collections4.IteratorUtils;
 +import org.apache.syncope.common.lib.types.AuditElements;
 +import org.apache.syncope.common.lib.types.AuditElements.Result;
 +import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
 +import org.apache.syncope.common.lib.types.TraceLevel;
 +import org.apache.syncope.core.persistence.api.dao.GroupDAO;
 +import org.apache.syncope.core.persistence.api.dao.TaskDAO;
 +import org.apache.syncope.core.persistence.api.dao.UserDAO;
 +import org.apache.syncope.core.persistence.api.entity.EntityFactory;
 +import org.apache.syncope.core.persistence.api.entity.task.PropagationTask;
 +import org.apache.syncope.core.persistence.api.entity.task.TaskExec;
 +import org.apache.syncope.core.provisioning.api.Connector;
 +import org.apache.syncope.core.provisioning.api.ConnectorFactory;
 +import org.apache.syncope.core.provisioning.api.TimeoutException;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationActions;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationReporter;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationTaskExecutor;
 +import org.apache.syncope.core.misc.AuditManager;
 +import org.apache.syncope.core.misc.spring.ApplicationContextProvider;
 +import org.apache.syncope.core.misc.utils.ConnObjectUtils;
 +import org.apache.syncope.core.misc.utils.ExceptionUtils2;
 +import org.apache.syncope.core.misc.utils.MappingUtils;
 +import org.apache.syncope.core.persistence.api.dao.AnyObjectDAO;
 +import org.apache.syncope.core.persistence.api.dao.VirSchemaDAO;
 +import org.apache.syncope.core.persistence.api.entity.Any;
 +import org.apache.syncope.core.persistence.api.entity.VirSchema;
 +import org.apache.syncope.core.persistence.api.entity.anyobject.AnyObject;
 +import org.apache.syncope.core.persistence.api.entity.group.Group;
 +import org.apache.syncope.core.persistence.api.entity.resource.ExternalResource;
 +import org.apache.syncope.core.persistence.api.entity.resource.MappingItem;
 +import org.apache.syncope.core.persistence.api.entity.resource.Provision;
 +import org.apache.syncope.core.persistence.api.entity.user.User;
 +import org.apache.syncope.core.provisioning.api.cache.VirAttrCache;
 +import org.apache.syncope.core.provisioning.api.cache.VirAttrCacheValue;
 +import org.apache.syncope.core.provisioning.api.notification.NotificationManager;
 +import org.apache.syncope.core.provisioning.api.propagation.PropagationException;
 +import org.identityconnectors.framework.common.exceptions.ConnectorException;
 +import org.identityconnectors.framework.common.objects.Attribute;
 +import org.identityconnectors.framework.common.objects.AttributeUtil;
 +import org.identityconnectors.framework.common.objects.ConnectorObject;
 +import org.identityconnectors.framework.common.objects.Name;
 +import org.identityconnectors.framework.common.objects.ObjectClass;
 +import org.identityconnectors.framework.common.objects.Uid;
 +import org.slf4j.Logger;
 +import org.slf4j.LoggerFactory;
 +import org.springframework.beans.factory.annotation.Autowired;
 +import org.springframework.beans.factory.support.AbstractBeanDefinition;
 +import org.springframework.transaction.annotation.Transactional;
 +
 +@Transactional(rollbackFor = { Throwable.class })
 +public abstract class AbstractPropagationTaskExecutor implements PropagationTaskExecutor {
 +
 +    protected static final Logger LOG = LoggerFactory.getLogger(PropagationTaskExecutor.class);
 +
 +    /**
 +     * Connector factory.
 +     */
 +    @Autowired
 +    protected ConnectorFactory connFactory;
 +
 +    /**
 +     * ConnObjectUtils.
 +     */
 +    @Autowired
 +    protected ConnObjectUtils connObjectUtils;
 +
 +    /**
 +     * Any object DAO.
 +     */
 +    @Autowired
 +    protected AnyObjectDAO anyObjectDAO;
 +
 +    /**
 +     * User DAO.
 +     */
 +    @Autowired
 +    protected UserDAO userDAO;
 +
 +    /**
 +     * User DAO.
 +     */
 +    @Autowired
 +    protected GroupDAO groupDAO;
 +
 +    /**
 +     * Task DAO.
 +     */
 +    @Autowired
 +    protected TaskDAO taskDAO;
 +
 +    @Autowired
 +    protected VirSchemaDAO virSchemaDAO;
 +
 +    /**
 +     * Notification Manager.
 +     */
 +    @Autowired
 +    protected NotificationManager notificationManager;
 +
 +    /**
 +     * Audit Manager.
 +     */
 +    @Autowired
 +    protected AuditManager auditManager;
 +
 +    @Autowired
 +    protected EntityFactory entityFactory;
 +
 +    @Autowired
 +    protected VirAttrCache virAttrCache;
 +
 +    @Override
 +    public TaskExec execute(final PropagationTask task) {
 +        return execute(task, null);
 +    }
 +
 +    protected List<PropagationActions> getPropagationActions(final ExternalResource resource) {
 +        List<PropagationActions> result = new ArrayList<>();
 +
 +        if (!resource.getPropagationActionsClassNames().isEmpty()) {
 +            for (String className : resource.getPropagationActionsClassNames()) {
 +                try {
 +                    Class<?> actionsClass = Class.forName(className);
 +                    result.add((PropagationActions) ApplicationContextProvider.getBeanFactory().
 +                            createBean(actionsClass, AbstractBeanDefinition.AUTOWIRE_BY_TYPE, true));
 +                } catch (ClassNotFoundException e) {
 +                    LOG.error("Invalid PropagationAction class name '{}' for resource {}", resource, className, e);
 +                }
 +            }
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Transform a {@link Collection} of {@link Attribute} instances into a {@link Map}.
 +     * The key to each element in the map is the {@code name} of an {@link Attribute}.
 +     * The value of each element in the map is the {@link Attribute} instance with that name.
 +     * <br/>
 +     * Different from the original because:
 +     * <ul>
 +     * <li>map keys are transformed toUpperCase()</li>
 +     * <li>returned map is mutable</li>
 +     * </ul>
 +     *
 +     * @param attributes set of attribute to transform to a map.
 +     * @return a map of string and attribute.
 +     *
 +     * @see org.identityconnectors.framework.common.objects.AttributeUtil#toMap(java.util.Collection)
 +     */
 +    private Map<String, Attribute> toMap(final Collection<? extends Attribute> attributes) {
 +        Map<String, Attribute> map = new HashMap<>();
 +        for (Attribute attr : attributes) {
 +            map.put(attr.getName().toUpperCase(), attr);
 +        }
 +        return map;
 +    }
 +
 +    protected void createOrUpdate(
 +            final PropagationTask task,
 +            final ConnectorObject beforeObj,
 +            final Connector connector,
 +            final Boolean[] propagationAttempted) {
 +
 +        // set of attributes to be propagated
 +        Set<Attribute> attributes = new HashSet<>(task.getAttributes());
 +
 +        // check if there is any missing or null / empty mandatory attribute
 +        Set<Object> mandatoryAttrNames = new HashSet<>();
 +        Attribute mandatoryMissing = AttributeUtil.find(MANDATORY_MISSING_ATTR_NAME, task.getAttributes());
 +        if (mandatoryMissing != null) {
 +            attributes.remove(mandatoryMissing);
 +
 +            if (beforeObj == null) {
 +                mandatoryAttrNames.addAll(mandatoryMissing.getValue());
 +            }
 +        }
 +        Attribute mandatoryNullOrEmpty = AttributeUtil.find(MANDATORY_NULL_OR_EMPTY_ATTR_NAME, task.getAttributes());
 +        if (mandatoryNullOrEmpty != null) {
 +            attributes.remove(mandatoryNullOrEmpty);
 +
 +            mandatoryAttrNames.addAll(mandatoryNullOrEmpty.getValue());
 +        }
 +        if (!mandatoryAttrNames.isEmpty()) {
 +            throw new IllegalArgumentException(
 +                    "Not attempted because there are mandatory attributes without value(s): " + mandatoryAttrNames);
 +        }
 +
 +        if (beforeObj == null) {
 +            LOG.debug("Create {} on {}", attributes, task.getResource().getKey());
 +            connector.create(new ObjectClass(task.getObjectClassName()), attributes, null, propagationAttempted);
 +        } else {
 +            // 1. check if rename is really required
 +            Name newName = (Name) AttributeUtil.find(Name.NAME, attributes);
 +
 +            LOG.debug("Rename required with value {}", newName);
 +
 +            if (newName != null && newName.equals(beforeObj.getName())
 +                    && !newName.getNameValue().equals(beforeObj.getUid().getUidValue())) {
 +
 +                LOG.debug("Remote object name unchanged");
 +                attributes.remove(newName);
 +            }
 +
 +            // 2. check wether anything is actually needing to be propagated, i.e. if there is attribute
 +            // difference between beforeObj - just read above from the connector - and the values to be propagated
 +            Map<String, Attribute> originalAttrMap = toMap(beforeObj.getAttributes());
 +            Map<String, Attribute> updateAttrMap = toMap(attributes);
 +
 +            // Only compare attribute from beforeObj that are also being updated
 +            Set<String> skipAttrNames = originalAttrMap.keySet();
 +            skipAttrNames.removeAll(updateAttrMap.keySet());
 +            for (String attrName : new HashSet<>(skipAttrNames)) {
 +                originalAttrMap.remove(attrName);
 +            }
 +
 +            Set<Attribute> originalAttrs = new HashSet<>(originalAttrMap.values());
 +
 +            if (originalAttrs.equals(attributes)) {
 +                LOG.debug("Don't need to propagate anything: {} is equal to {}", originalAttrs, attributes);
 +            } else {
 +                LOG.debug("Attributes that would be updated {}", attributes);
 +
 +                Set<Attribute> strictlyModified = new HashSet<>();
 +                for (Attribute attr : attributes) {
 +                    if (!originalAttrs.contains(attr)) {
 +                        strictlyModified.add(attr);
 +                    }
 +                }
 +
 +                // 3. provision entry
 +                LOG.debug("Update {} on {}", strictlyModified, task.getResource().getKey());
 +
 +                connector.update(
 +                        beforeObj.getObjectClass(), beforeObj.getUid(), strictlyModified, null, propagationAttempted);
 +            }
 +        }
 +    }
 +
 +    protected Any<?> getAny(final PropagationTask task) {
 +        Any<?> any = null;
 +
 +        if (task.getAnyKey() != null) {
 +            switch (task.getAnyTypeKind()) {
 +                case USER:
 +                    try {
 +                        any = userDAO.authFind(task.getAnyKey());
 +                    } catch (Exception e) {
 +                        LOG.error("Could not read user {}", task.getAnyKey(), e);
 +                    }
 +                    break;
 +
 +                case GROUP:
 +                    try {
 +                        any = groupDAO.authFind(task.getAnyKey());
 +                    } catch (Exception e) {
 +                        LOG.error("Could not read group {}", task.getAnyKey(), e);
 +                    }
 +                    break;
 +
 +                case ANY_OBJECT:
 +                default:
 +                    try {
 +                        any = anyObjectDAO.authFind(task.getAnyKey());
 +                    } catch (Exception e) {
 +                        LOG.error("Could not read any object {}", task.getAnyKey(), e);
 +                    }
 +                    break;
 +            }
 +        }
 +
 +        return any;
 +    }
 +
 +    protected void delete(
 +            final PropagationTask task,
 +            final ConnectorObject beforeObj,
 +            final Connector connector,
 +            final Boolean[] propagationAttempted) {
 +
 +        if (beforeObj == null) {
 +            LOG.debug("{} not found on external resource: ignoring delete", task.getConnObjectKey());
 +        } else {
 +            /*
 +             * We must choose here whether to
 +             * a. actually delete the provided any object from the external resource
 +             * b. just update the provided any object data onto the external resource
 +             *
 +             * (a) happens when either there is no any object associated with the PropagationTask (this takes place
 +             * when the task is generated via UserLogic.delete() / GroupLogic.delete()) or the provided updated
 +             * any object hasn't the current resource assigned (when the task is generated via
 +             * UserController.update() / GroupLogic.update()).
 +             *
 +             * (b) happens when the provided updated any object does have the current resource assigned (when the task
 +             * is generated via UserLogic.update() / GroupLogic.updae()): this basically means that before such
 +             * update, this any object used to have the current resource assigned by more than one mean (for example,
 +             * two different memberships with the same resource).
 +             */
 +            Any<?> any = getAny(task);
 +            Collection<String> resources = any instanceof User
 +                    ? userDAO.findAllResourceNames((User) any)
 +                    : any instanceof AnyObject
 +                            ? anyObjectDAO.findAllResourceNames((AnyObject) any)
 +                            : any instanceof Group
 +                                    ? ((Group) any).getResourceNames()
 +                                    : Collections.<String>emptySet();
 +            if (!resources.contains(task.getResource().getKey())) {
 +                LOG.debug("Delete {} on {}", beforeObj.getUid(), task.getResource().getKey());
 +
 +                connector.delete(beforeObj.getObjectClass(), beforeObj.getUid(), null, propagationAttempted);
 +            } else {
 +                createOrUpdate(task, beforeObj, connector, propagationAttempted);
 +            }
 +        }
 +    }
 +
 +    @Override
 +    public TaskExec execute(final PropagationTask task, final PropagationReporter reporter) {
 +        List<PropagationActions> actions = getPropagationActions(task.getResource());
 +
 +        Date startDate = new Date();
 +
 +        TaskExec execution = entityFactory.newEntity(TaskExec.class);
 +        execution.setStatus(PropagationTaskExecStatus.CREATED.name());
 +
 +        String taskExecutionMessage = null;
 +        String failureReason = null;
 +
 +        // Flag to state whether any propagation has been attempted
 +        Boolean[] propagationAttempted = new Boolean[] { false };
 +
 +        ConnectorObject beforeObj = null;
 +        ConnectorObject afterObj = null;
 +
 +        Provision provision = null;
 +        Connector connector = null;
 +        Result result;
 +        try {
 +            provision = task.getResource().getProvision(new ObjectClass(task.getObjectClassName()));
 +            connector = connFactory.getConnector(task.getResource());
 +
 +            // Try to read remote object BEFORE any actual operation
 +            beforeObj = getRemoteObject(task, connector, provision, false);
 +
 +            for (PropagationActions action : actions) {
 +                action.before(task, beforeObj);
 +            }
 +
 +            switch (task.getOperation()) {
 +                case CREATE:
 +                case UPDATE:
 +                    createOrUpdate(task, beforeObj, connector, propagationAttempted);
 +                    break;
 +
 +                case DELETE:
 +                    delete(task, beforeObj, connector, propagationAttempted);
 +                    break;
 +
 +                default:
 +            }
 +
 +            execution.setStatus(propagationAttempted[0]
 +                    ? PropagationTaskExecStatus.SUCCESS.name()
 +                    : PropagationTaskExecStatus.NOT_ATTEMPTED.name());
 +
 +            for (PropagationActions action : actions) {
 +                action.after(task, execution, afterObj);
 +            }
 +
 +            LOG.debug("Successfully propagated to {}", task.getResource());
 +            result = Result.SUCCESS;
 +        } catch (Exception e) {
 +            result = Result.FAILURE;
 +            LOG.error("Exception during provision on resource " + task.getResource().getKey(), e);
 +
 +            if (e instanceof ConnectorException && e.getCause() != null) {
 +                taskExecutionMessage = e.getCause().getMessage();
 +                if (e.getCause().getMessage() == null) {
 +                    failureReason = e.getMessage();
 +                } else {
 +                    failureReason = e.getMessage() + "\n\n Cause: " + e.getCause().getMessage().split("\n")[0];
 +                }
 +            } else {
 +                taskExecutionMessage = ExceptionUtils2.getFullStackTrace(e);
 +                if (e.getCause() == null) {
 +                    failureReason = e.getMessage();
 +                } else {
 +                    failureReason = e.getMessage() + "\n\n Cause: " + e.getCause().getMessage().split("\n")[0];
 +                }
 +            }
 +
 +            try {
 +                execution.setStatus(PropagationTaskExecStatus.FAILURE.name());
 +            } catch (Exception wft) {
 +                LOG.error("While executing KO action on {}", execution, wft);
 +            }
 +
 +            propagationAttempted[0] = true;
 +
 +            for (PropagationActions action : actions) {
 +                action.onError(task, execution, e);
 +            }
 +        } finally {
 +            // Try to read remote object AFTER any actual operation
 +            if (connector != null) {
 +                try {
 +                    afterObj = getRemoteObject(task, connector, provision, true);
 +                } catch (Exception ignore) {
 +                    // ignore exception
 +                    LOG.error("Error retrieving after object", ignore);
 +                }
 +            }
 +
 +            execution.setStartDate(startDate);
 +            execution.setMessage(taskExecutionMessage);
 +            execution.setEndDate(new Date());
 +
 +            LOG.debug("Execution finished: {}", execution);
 +
 +            if (hasToBeregistered(task, execution)) {
 +                LOG.debug("Execution to be stored: {}", execution);
 +
 +                execution.setTask(task);
 +                task.addExec(execution);
 +
 +                taskDAO.save(task);
 +                // needed to generate a value for the execution key
 +                taskDAO.flush();
 +            }
 +
 +            if (reporter != null) {
 +                reporter.onSuccessOrNonPriorityResourceFailures(
 +                        task,
 +                        PropagationTaskExecStatus.valueOf(execution.getStatus()),
 +                        failureReason,
 +                        beforeObj,
 +                        afterObj);
 +            }
 +        }
 +
 +        notificationManager.createTasks(
 +                AuditElements.EventCategoryType.PROPAGATION,
 +                task.getAnyTypeKind().name().toLowerCase(),
 +                task.getResource().getKey(),
 +                task.getOperation().name().toLowerCase(),
 +                result,
 +                beforeObj, // searching for before object is too much expensive ... 
 +                new Object[] { execution, afterObj },
 +                task);
 +
 +        auditManager.audit(
 +                AuditElements.EventCategoryType.PROPAGATION,
 +                task.getAnyTypeKind().name().toLowerCase(),
 +                task.getResource().getKey(),
 +                task.getOperation().name().toLowerCase(),
 +                result,
 +                beforeObj, // searching for before object is too much expensive ... 
 +                new Object[] { execution, afterObj },
 +                task);
 +
 +        return execution;
 +    }
 +
 +    @Override
 +    public void execute(final Collection<PropagationTask> tasks) {
 +        execute(tasks, null, false);
 +    }
 +
 +    protected abstract void doExecute(
 +            Collection<PropagationTask> tasks, PropagationReporter reporter, boolean nullPriorityAsync);
 +
 +    @Override
 +    public void execute(
 +            final Collection<PropagationTask> tasks,
 +            final PropagationReporter reporter,
 +            final boolean nullPriorityAsync) {
 +
 +        try {
 +            doExecute(tasks, reporter, nullPriorityAsync);
 +        } catch (PropagationException e) {
 +            LOG.error("Error propagation priority resource", e);
 +            reporter.onPriorityResourceFailure(e.getResourceName(), tasks);
 +        }
 +    }
 +
 +    /**
 +     * Check whether an execution has to be stored, for a given task.
 +     *
 +     * @param task propagation task
 +     * @param execution to be decide whether to store or not
 +     * @return true if execution has to be store, false otherwise
 +     */
 +    protected boolean hasToBeregistered(final PropagationTask task, final TaskExec execution) {
 +        boolean result;
 +
 +        boolean failed = PropagationTaskExecStatus.valueOf(execution.getStatus()) != PropagationTaskExecStatus.SUCCESS;
 +
 +        switch (task.getOperation()) {
 +
 +            case CREATE:
 +                result = (failed && task.getResource().getCreateTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
 +                        || task.getResource().getCreateTraceLevel() == TraceLevel.ALL;
 +                break;
 +
 +            case UPDATE:
 +                result = (failed && task.getResource().getUpdateTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
 +                        || task.getResource().getUpdateTraceLevel() == TraceLevel.ALL;
 +                break;
 +
 +            case DELETE:
 +                result = (failed && task.getResource().getDeleteTraceLevel().ordinal() >= TraceLevel.FAILURES.ordinal())
 +                        || task.getResource().getDeleteTraceLevel() == TraceLevel.ALL;
 +                break;
 +
 +            default:
 +                result = false;
 +        }
 +
 +        return result;
 +    }
 +
 +    /**
 +     * Get remote object for given task.
 +     *
 +     * @param connector connector facade proxy.
 +     * @param task current propagation task.
 +     * @param provision provision
 +     * @param latest 'FALSE' to retrieve object using old connObjectKey if not null.
 +     * @return remote connector object.
 +     */
 +    protected ConnectorObject getRemoteObject(
 +            final PropagationTask task,
 +            final Connector connector,
 +            final Provision provision,
 +            final boolean latest) {
 +
 +        String connObjectKey = latest || task.getOldConnObjectKey() == null
 +                ? task.getConnObjectKey()
 +                : task.getOldConnObjectKey();
 +
 +        List<MappingItem> linkingMappingItems = new ArrayList<>();
 +        for (VirSchema schema : virSchemaDAO.findByProvision(provision)) {
 +            linkingMappingItems.add(schema.asLinkingMappingItem());
 +        }
 +
 +        ConnectorObject obj = null;
 +        try {
 +            obj = connector.getObject(
-                     task.getOperation(),
 +                    new ObjectClass(task.getObjectClassName()),
 +                    new Uid(connObjectKey),
 +                    MappingUtils.buildOperationOptions(IteratorUtils.chainedIterator(
-                                     MappingUtils.getPropagationMappingItems(provision).iterator(),
-                                     linkingMappingItems.iterator())));
++                            MappingUtils.getPropagationMappingItems(provision).iterator(),
++                            linkingMappingItems.iterator())));
 +
 +            for (MappingItem item : linkingMappingItems) {
 +                Attribute attr = obj.getAttributeByName(item.getExtAttrName());
 +                if (attr == null) {
 +                    virAttrCache.expire(task.getAnyType(), task.getAnyKey(), item.getIntAttrName());
 +                } else {
 +                    VirAttrCacheValue cacheValue = new VirAttrCacheValue();
 +                    cacheValue.setValues(attr.getValue());
 +                    virAttrCache.put(task.getAnyType(), task.getAnyKey(), item.getIntAttrName(), cacheValue);
 +                }
 +            }
 +        } catch (TimeoutException toe) {
 +            LOG.debug("Request timeout", toe);
 +            throw toe;
 +        } catch (RuntimeException ignore) {
 +            LOG.debug("While resolving {}", connObjectKey, ignore);
 +        }
 +
 +        return obj;
 +    }
 +}