org.lsc.AbstractSynchronize.java Source code

Java tutorial

Introduction

Here is the source code for org.lsc.AbstractSynchronize.java

Source

/*
 ****************************************************************************
 * Ldap Synchronization Connector provides tools to synchronize
 * electronic identities from a list of data sources including
 * any database with a JDBC connector, another LDAP directory,
 * flat files...
 *
 *                  ==LICENSE NOTICE==
 * 
 * Copyright (c) 2008 - 2011 LSC Project 
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
    
 *  * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 *   * Neither the name of the LSC Project nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *                  ==LICENSE NOTICE==
 *
 *               (c) 2008 - 2011 LSC Project
 *         Sebastien Bahloul <seb@lsc-project.org>
 *         Thomas Chemineau <thomas@lsc-project.org>
 *         Jonathan Clarke <jon@lsc-project.org>
 *         Remy-Christophe Schermesser <rcs@lsc-project.org>
 ****************************************************************************
 */
package org.lsc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import javax.naming.CommunicationException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.lsc.LscDatasetModification.LscDatasetModificationType;
import org.lsc.beans.BeanComparator;
import org.lsc.beans.IBean;
import org.lsc.beans.syncoptions.ISyncOptions;
import org.lsc.configuration.LscConfiguration;
import org.lsc.configuration.PivotTransformationType.Transformation;
import org.lsc.exception.LscServiceCommunicationException;
import org.lsc.exception.LscServiceException;
import org.lsc.service.IAsynchronousService;
import org.lsc.service.IService;
import org.lsc.utils.LSCStructuralLogger;
import org.lsc.utils.ScriptingEvaluator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Abstract main class to derive.
 * 
 * @author Sebastien Bahloul &lt;seb@lsc-project.org&gt;
 */
public abstract class AbstractSynchronize {

    static final Logger LOGGER = LoggerFactory.getLogger(AbstractSynchronize.class);

    /** List of configured options. */
    private static Options options = new Options();

    static {
        options.addOption("nc", "nocreate", false, "Don't create any entry");
        options.addOption("nu", "noupdate", false, "Don't update");
        options.addOption("nd", "nodelete", false, "Don't delete");
        options.addOption("nr", "nomodrdn", false, "Don't rename (MODRDN)");
        options.addOption("n", "dryrun", false, "Don't update the directory at all");
    }

    /**
     * This is the flag to prevent entries add operation in the target
     * directory.
     */
    protected boolean nocreate = false;

    /**
     * This is the flag to prevent entries update operation in the target
     * directory.
     */
    protected boolean noupdate = false;

    /**
     * This is the flag to prevent entries delete operation in the target
     * directory.
     */
    protected boolean nodelete = false;

    /**
     * This is the flag to prevent entries modrdn operation in the target
     * directory.
     */
    protected boolean nomodrdn = false;

    /**
     * Number of parallel threads handling synchronization and cleaning
     * Default to 5
     */
    private int threads;

    /**
     * Maximum time waiting for synchronizing threads tasks to finish (in seconds)
     * This is the global synchronization task time - 3600 by default
     */
    private int timeLimit;

    /**
     * Map used to keep trace of all running threads
     */
    private Map<String, Thread> asynchronousThreads;

    /**
     * Map used to get SyncrhonizeTaks from task name
     */
    private Map<String, AsynchronousRunner> mapSTasks;

    /**
     * Default constructor.
     */
    protected AbstractSynchronize() {
        timeLimit = 3600;
        asynchronousThreads = new HashMap<String, Thread>();
        mapSTasks = new HashMap<String, AsynchronousRunner>();
    }

    /**
     * Clean the destination LDAP directory (delete objects not present in
     * source).
     * 
     * @param syncName
     *            the synchronization name
     * @param srcService
     *            the source service (JDBC or JNDI)
     * @param dstJndiService
     *            the jndi destination service
     */
    protected final boolean clean2Ldap(Task task) {

        InfoCounter counter = new InfoCounter();

        // Get list of all entries from the destination
        Set<Entry<String, LscDatasets>> ids = null;
        try {
            ids = task.getDestinationService().getListPivots().entrySet();
        } catch (LscServiceException e) {
            LOGGER.error("Error getting list of IDs in the destination for task {}", task.getName());
            LOGGER.debug(e.toString(), e);
            return false;
        }

        // Make sure we have at least one entry to work on
        if (ids.isEmpty()) {
            LOGGER.error("Empty or non existant destination (no IDs found)");
            return false;
        }

        ISyncOptions syncOptions = task.getSyncOptions();

        LscModifications lm = null;

        /** Hash table to pass objects into JavaScript condition */
        Map<String, Object> conditionObjects = null;

        IBean taskBean;

        // Loop on all entries in the destination and delete them if they're not
        // found in the source
        for (Entry<String, LscDatasets> id : ids) {
            counter.incrementCountAll();

            try {
                // Search for the corresponding object in the source
                taskBean = getBean(task, task.getSourceService(), id.getKey(), id.getValue(), false, false);

                // If we didn't find the object in the source, delete it in the
                // destination
                if (taskBean == null) {
                    // Retrieve condition to evaluate before deleting
                    Boolean doDelete = null;
                    String conditionString = syncOptions.getDeleteCondition();

                    // Don't use JavaScript evaluator for primitive cases
                    if (conditionString.matches("true")) {
                        doDelete = true;
                    } else if (conditionString.matches("false")) {
                        doDelete = false;
                    } else {
                        IBean dstBean = getBean(task, task.getDestinationService(), id.getKey(), id.getValue(),
                                true, false);
                        // Log an error if the bean could not be retrieved!
                        // This shouldn't happen.
                        if (dstBean == null) {
                            LOGGER.error("Could not retrieve the object {} from the directory!", id.getKey());
                            counter.incrementCountError();
                            continue;
                        }

                        // Put the bean in a map to pass to JavaScript
                        // evaluator
                        conditionObjects = new HashMap<String, Object>();
                        conditionObjects.put("dstBean", dstBean);
                        conditionObjects.putAll(task.getScriptingVars());

                        // Evaluate if we have to do something
                        doDelete = ScriptingEvaluator.evalToBoolean(task, conditionString, conditionObjects);
                    }

                    if (doDelete) {
                        lm = new LscModifications(LscModificationType.DELETE_OBJECT, task.getName());
                        lm.setMainIdentifer(id.getKey());

                        List<LscDatasetModification> attrsMod = new ArrayList<LscDatasetModification>();
                        for (Entry<String, Object> attr : id.getValue().getDatasets().entrySet()) {
                            attrsMod.add(new LscDatasetModification(LscDatasetModificationType.DELETE_VALUES,
                                    attr.getKey(), Collections.singletonList(attr.getValue())));
                        }
                        lm.setLscAttributeModifications(attrsMod);

                        counter.incrementCountModifiable();

                        // if "nodelete" was specified in command line options,
                        // log action for debugging purposes and continue
                        if (nodelete) {
                            logShouldAction(lm, task.getName());
                            continue;
                        }
                    } else {
                        continue;
                    }

                    // if we got here, we have a modification to apply - let's
                    // do it!
                    if (task.getDestinationService().apply(lm)) {
                        counter.incrementCountCompleted();
                        logAction(lm, id, task.getName());
                    } else {
                        counter.incrementCountError();
                        logActionError(lm, id.getValue(), new Exception(
                                "Technical problem while applying modifications to destination service"));
                    }
                }
            } catch (LscServiceException e) {
                counter.incrementCountError();
                logActionError(lm, id.getValue(), e);
                if (e.getCause().getClass().isAssignableFrom(CommunicationException.class)) {
                    // we lost the connection to the source or destination, stop
                    // everything!
                    LOGGER.error("Connection lost! Aborting.");
                    return false;
                } else {
                    LOGGER.error("Unable to delete object {} ({})", id.getKey(), e.toString());
                }
            }
        }

        logStatus(counter);
        return counter.getCountError() == 0;
    }

    /**
     * Synchronize the destination LDAP directory (create and update objects
     * from source).
     * 
     * @param syncName
     *            the synchronization name
     * @param srcService
     *            the source service (JDBC or JNDI or anything else)
     * @param dstService
     *            the JNDI destination service
     * @param objectBean
     * @param customLibrary
     */
    protected final boolean synchronize2Ldap(final Task task) {
        /*final String syncName,
        final IService srcService, final IService dstService,
        final Object customLibrary*/

        InfoCounter counter = new InfoCounter();
        // Get list of all entries from the source
        Set<Entry<String, LscDatasets>> ids = null;
        SynchronizeThreadPoolExecutor threadPool = null;

        try {
            ids = task.getSourceService().getListPivots().entrySet();
        } catch (Exception e) {
            LOGGER.error("Error getting list of IDs in the source for task {}", task.getName());
            LOGGER.debug(e.toString(), e);
            return false;
        }

        // Make sure we have at least one entry to work on
        if (ids.isEmpty()) {
            LOGGER.error("Empty or non existant source (no IDs found)");
            return false;
        }

        threadPool = new SynchronizeThreadPoolExecutor(getThreads());

        /*
         * Loop on all entries in the source and add or update them in the
         * destination
         */
        for (Entry<String, LscDatasets> id : ids) {
            threadPool.runTask(new SynchronizeTask(task, counter, this, id, true));
        }
        try {
            threadPool.shutdown();
            threadPool.awaitTermination(timeLimit, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            LOGGER.error("Tasks terminated according to time limit: " + e.toString(), e);
            LOGGER.info("If you want to avoid this message, "
                    + "increase the time limit by using dedicated parameter.");
        }

        logStatus(counter);
        return counter.getCountError() == 0;
    }

    public final synchronized void startAsynchronousSynchronize2Ldap(Task task) {

        AsynchronousRunner asyncRunner = new AsynchronousRunner(task, this);
        String taskName = task.getName();

        Thread thread = new Thread(asyncRunner);
        thread.setName(taskName);
        asynchronousThreads.put(taskName, thread);
        mapSTasks.put(taskName, asyncRunner);
        thread.start();
    }

    public final synchronized void shutdownAsynchronousSynchronize2Ldap(final String syncName, boolean forceStop) {
        Thread asyncThread = asynchronousThreads.get(syncName);
        long startTime = System.currentTimeMillis();

        if (asyncThread == null) {
            LOGGER.info("Trying to stop a non running asynchronous task: " + syncName);
            return;
        }

        while (asyncThread.isAlive()) {
            try {
                asyncThread.join(1000);
                if ((System.currentTimeMillis() - startTime) > 5000) {
                    if (forceStop) {
                        // After 5 secondes, leaving
                        asyncThread.interrupt();
                        asyncThread.join(1000);
                    } else {
                        break;
                    }
                }
            } catch (InterruptedException ie) {
                // Thread has been interrupted, doing nothing
            }
        }
        if (!asyncThread.isAlive()) {
            asynchronousThreads.remove(syncName);
            mapSTasks.remove(syncName);
        }
    }

    public final boolean isAsynchronousTaskRunning(final String syncName) {
        Thread asyncThread = asynchronousThreads.get(syncName);
        if (asyncThread != null) {
            if (asyncThread.isAlive()) {
                return true;
            } else {
                asynchronousThreads.remove(syncName);
                mapSTasks.remove(syncName);
                return false;
            }
        } else {
            return false;
        }
    }

    public final String getTaskFullStatus(final String syncName) {
        Thread asyncThread = asynchronousThreads.get(syncName);
        if (asyncThread != null && asyncThread.isAlive()) {
            AsynchronousRunner asyncRunner = mapSTasks.get(syncName);
            InfoCounter counter = asyncRunner.getCounter();
            return getLogStatus(counter);
        } else {
            return null;
        }
    }

    public abstract boolean isAsynchronousTask(String taskName);

    public abstract Task[] getTasks();

    public abstract Task getTask(String taskName);

    /**
     * Log all effective action.
     * 
     * @param jm
     *            List of modification to do on the Ldap server
     * @param identifier
     *            object identifier
     * @param except
     *            synchronization process name
     */
    protected final void logActionError(final LscModifications lm, final Object data, final Exception except) {

        LOGGER.error("Error while synchronizing ID {}: {}", (lm != null ? lm.getMainIdentifier() : data),
                except.toString());
        LOGGER.debug(except.toString(), except);

        if (lm != null) {
            // TODO Fix LdifLogger to avoid this
            LOGGER.error("", lm);
        }
    }

    /**
     * Log all effective action.
     * 
     * @param jm
     *            List of modification to do on the Ldap server
     * @param id
     *            object identifier
     * @param syncName
     *            synchronization process name
     */
    protected final void logAction(final LscModifications lm, final Entry<String, LscDatasets> id,
            final String syncName) {
        switch (lm.getOperation()) {
        case CREATE_OBJECT:
            LSCStructuralLogger.DESTINATION.info("# Adding new object {} for {}", lm.getMainIdentifier(), syncName);
            break;

        case UPDATE_OBJECT:
            LSCStructuralLogger.DESTINATION.info("# Updating object {} for {}", lm.getMainIdentifier(), syncName);
            break;

        case CHANGE_ID:
            LSCStructuralLogger.DESTINATION.info("# Renaming object {} for {}", lm.getMainIdentifier(), syncName);
            break;

        case DELETE_OBJECT:
            LSCStructuralLogger.DESTINATION.info("# Removing object {} for {}", lm.getMainIdentifier(), syncName);
            break;

        default:
            LSCStructuralLogger.DESTINATION.info("Error: unknown changetype ({} for {})", lm.getMainIdentifier(),
                    syncName);
        }

        // TODO Fix LdifLogger to avoid this
        LSCStructuralLogger.DESTINATION.info("", lm);
    }

    /**
     * @param jm
     * @param id
     * @param syncName
     */
    protected final void logShouldAction(final LscModifications lm, final String syncName) {
        switch (lm.getOperation()) {
        case CREATE_OBJECT:
            LSCStructuralLogger.DESTINATION.debug("Create condition false. Should have added object {}",
                    lm.getMainIdentifier());
            break;

        case UPDATE_OBJECT:
            LSCStructuralLogger.DESTINATION.debug("Update condition false. Should have modified object {}",
                    lm.getMainIdentifier());
            break;

        case CHANGE_ID:
            LSCStructuralLogger.DESTINATION.debug("ModRDN condition false. Should have renamed object {}",
                    lm.getMainIdentifier());
            break;

        case DELETE_OBJECT:
            LSCStructuralLogger.DESTINATION.debug("Delete condition false. Should have removed object {}",
                    lm.getMainIdentifier());
            break;

        default:
            LSCStructuralLogger.DESTINATION.debug("Error: unknown changetype ({} for {})", lm.getMainIdentifier(),
                    syncName);
        }

        // TODO Fix LdifLogger to avoid this
        LSCStructuralLogger.DESTINATION.debug("", lm);
    }

    protected void logStatus(InfoCounter counter) {
        String totalsLogMessage = getLogStatus(counter);
        if (counter.getCountError() > 0) {
            LOGGER.error(totalsLogMessage);
        } else {
            LOGGER.info(totalsLogMessage);
        }
    }

    protected String getLogStatus(InfoCounter counter) {
        String totalsLogMessage = "All entries: " + counter.getCountAll() + ", to modify entries: "
                + counter.getCountModifiable() + ", successfully modified entries: " + counter.getCountCompleted()
                + ", errors: " + counter.getCountError();
        return totalsLogMessage;
    }

    protected IBean getBean(Task task, IService service, String pivotName, LscDatasets pivotAttributes,
            boolean fromSameService, boolean fromSource) throws LscServiceException {
        List<Transformation> transformations = LscConfiguration.getPivotTransformation(task.getTaskType());
        if (!fromSameService && transformations != null) {
            LscDatasets newPivots = new LscDatasets(pivotAttributes.getDatasets());
            for (Entry<String, Object> pivot : pivotAttributes.getDatasets().entrySet()) {
                for (Transformation transformation : transformations) {
                    if (pivot.getKey().equalsIgnoreCase(transformation.getFromAttribute()) && LscConfiguration
                            .pivotOriginMatchesFromSource(transformation.getPivotOrigin(), fromSource)) {
                        newPivots.put(transformation.getToAttribute(),
                                transform(task, transformation, pivot.getValue()));
                    }
                }
            }
            return service.getBean(pivotName, newPivots, fromSameService);
        }
        return service.getBean(pivotName, pivotAttributes, fromSameService);
    }

    protected Object transform(Task task, Transformation transformation, Object value) throws LscServiceException {
        Map<String, Object> javaScriptObjects = new HashMap<String, Object>();
        javaScriptObjects.put("value", value);
        if (task.getCustomLibraries() != null) {
            javaScriptObjects.put("custom", task.getCustomLibraries());
        }
        javaScriptObjects.putAll(task.getScriptingVars());
        if (LscConfiguration.isLdapBinaryAttribute(transformation.getToAttribute())) {
            return ScriptingEvaluator.evalToByteArray(task, transformation.getValue(), javaScriptObjects);
        } else {
            return ScriptingEvaluator.evalToString(task, transformation.getValue(), javaScriptObjects);
        }

    }

    /**
     * Parse the command line arguments according the selected filter.
     * 
     * @param cmdLine
     *            Command line options
     * @return the parsing status
     */
    public final boolean parseOptions(final CommandLine cmdLine) {
        if (cmdLine.hasOption("nc")) {
            nocreate = true;
        }
        if (cmdLine.hasOption("nu")) {
            noupdate = true;
        }
        if (cmdLine.hasOption("nd")) {
            nodelete = true;
        }
        if (cmdLine.hasOption("nr")) {
            nomodrdn = true;
        }
        if (cmdLine.hasOption("n")) {
            nocreate = true;
            noupdate = true;
            nodelete = true;
            nomodrdn = true;
        }
        return true;
    }

    /**
     * Get options against which the command line is analyzed.
     * 
     * @return the options
     */
    public static final Options getOptions() {
        return options;
    }

    /**
     * Parallel synchronizing threads accessor
     * 
     * @return the number of parallels threads dedicated to tasks
     *         synchronization
     */
    public int getThreads() {
        return threads;
    }

    /**
     * Parallel synchronizing threads accessor
     * 
     * @param threads
     *            the number of parallels threads dedicated to tasks
     *            synchronization
     */
    public void setThreads(int threads) {
        this.threads = threads;
    }

    /**
     * Time limit accessor
     * @return the number of seconds
     */
    public int getTimeLimit() {
        return timeLimit;
    }

    /**
     * Time limit accessor
     * 
     * @param timeLimit number of seconds
     */
    public void setTimeLimit(int timeLimit) {
        this.timeLimit = timeLimit;
    }

}

class AsynchronousRunner implements Runnable {

    static final Logger LOGGER = LoggerFactory.getLogger(AsynchronousRunner.class);

    private AbstractSynchronize abstractSynchronize;
    private Task task;
    private InfoCounter counter;

    public AsynchronousRunner(Task task, AbstractSynchronize abstractSynchronize) {
        this.task = task;
        this.abstractSynchronize = abstractSynchronize;
    }

    public void run() {
        counter = new InfoCounter();

        SynchronizeThreadPoolExecutor threadPool = new SynchronizeThreadPoolExecutor(
                abstractSynchronize.getThreads());

        Entry<String, LscDatasets> nextId = null;
        try {
            IAsynchronousService aService = null;
            boolean fromSource = true;
            if (task.getDestinationService() instanceof IAsynchronousService) {
                aService = (IAsynchronousService) task.getDestinationService();
                fromSource = false;
            } else if (task.getSourceService() instanceof IAsynchronousService) {
                aService = (IAsynchronousService) task.getSourceService();
            } else {
                LOGGER.error(
                        "LSC should never reach this point! Please consider debugging the code because we are trying to launch an asynchronous sync without any asynchronous service!");
                return;
            }

            AbstractSynchronize.LOGGER.debug("Asynchronous synchronize {}", task.getName());

            boolean interrupted = false;
            while (!interrupted) {
                nextId = aService.getNextId();
                if (nextId != null) {
                    threadPool.runTask(new SynchronizeTask(task, counter, abstractSynchronize, nextId, fromSource));
                } else {
                    try {
                        Thread.sleep(aService.getInterval());
                    } catch (InterruptedException e) {
                        AbstractSynchronize.LOGGER.debug("Synchronization thread interrupted !");
                        interrupted = true;
                    }
                }
            }
        } catch (LscServiceException e) {
            counter.incrementCountError();
            abstractSynchronize.logActionError(null, nextId, e);
        }

        try {
            threadPool.shutdown();
            threadPool.awaitTermination(abstractSynchronize.getTimeLimit(), TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            LOGGER.error("Tasks terminated according to time limit: " + e.toString(), e);
            LOGGER.info("If you want to avoid this message, "
                    + "increase the time limit by using dedicated parameter.");
        }

    }

    public InfoCounter getCounter() {
        return counter;
    }
}

/**
 * @author sbahloul
 */
class SynchronizeTask implements Runnable {

    static final Logger LOGGER = LoggerFactory.getLogger(SynchronizeTask.class);

    private String syncName;
    private InfoCounter counter;
    private AbstractSynchronize abstractSynchronize;
    private Entry<String, LscDatasets> id;
    private Task task;
    private boolean fromSource;

    public SynchronizeTask(final Task task, InfoCounter counter, AbstractSynchronize abstractSynchronize,
            Entry<String, LscDatasets> id, boolean fromSource) {
        this.syncName = task.getName();
        this.counter = counter;
        this.task = task;
        this.abstractSynchronize = abstractSynchronize;
        this.id = id;
        this.fromSource = fromSource;
    }

    public void run() {
        counter.incrementCountAll();
        try {
            run(abstractSynchronize.getBean(task,
                    fromSource ? task.getSourceService() : task.getDestinationService(), id.getKey(), id.getValue(),
                    true, fromSource));
        } catch (RuntimeException e) {
            counter.incrementCountError();
            abstractSynchronize.logActionError(null, id.getValue(), e);

            if (e.getCause() instanceof LscServiceCommunicationException) {
                AbstractSynchronize.LOGGER.error("Connection lost! Aborting.");
            }
        } catch (Exception e) {
            counter.incrementCountError();
            abstractSynchronize.logActionError(null, id.getValue(), e);
        }
    }

    public boolean run(IBean entry) {

        LscModifications lm = null;
        IBean dstBean = null;
        /** Hash table to pass objects into JavaScript condition */
        Map<String, Object> conditionObjects = null;

        try {
            /*
             * Log an error if the source object could not be retrieved! This
             * shouldn't happen.
             */
            if (entry == null) {
                counter.incrementCountError();
                AbstractSynchronize.LOGGER
                        .error("Synchronization aborted because no source object has been found !");
                return false;
            }

            // Search destination for matching object
            if (id != null) {
                dstBean = abstractSynchronize.getBean(task, task.getDestinationService(), id.getKey(),
                        id.getValue(), !fromSource, fromSource);
            } else {
                LscDatasets entryDatasets = new LscDatasets();
                for (String datasetName : entry.datasets().getAttributesNames()) {
                    entryDatasets.getDatasets().put(datasetName, entry.getDatasetById(datasetName));
                }
                dstBean = abstractSynchronize.getBean(task, task.getDestinationService(), entry.getMainIdentifier(),
                        entryDatasets, !fromSource, fromSource);
            }

            // Calculate operation that would be performed
            LscModificationType modificationType = BeanComparator.calculateModificationType(task, entry, dstBean);

            // Retrieve condition to evaluate before creating/updating
            Boolean applyCondition = null;
            String conditionString = task.getSyncOptions().getCondition(modificationType);

            // Don't use JavaScript evaluator for primitive cases
            if (conditionString.matches("true")) {
                applyCondition = true;
            } else if (conditionString.matches("false")) {
                applyCondition = false;
            } else {
                conditionObjects = new HashMap<String, Object>();
                conditionObjects.put("dstBean", dstBean);
                conditionObjects.put("srcBean", entry);
                conditionObjects.putAll(task.getScriptingVars());

                // Evaluate if we have to do something
                applyCondition = ScriptingEvaluator.evalToBoolean(task, conditionString, conditionObjects);
            }

            if (applyCondition) {
                lm = BeanComparator.calculateModifications(task, entry, dstBean);

                // if there's nothing to do, skip to the next object
                if (lm == null) {
                    return true;
                }

                counter.incrementCountModifiable();

                // no modification: log action for debugging purposes and forget
                if ((modificationType == LscModificationType.CREATE_OBJECT && abstractSynchronize.nocreate)
                        || (modificationType == LscModificationType.UPDATE_OBJECT && abstractSynchronize.noupdate)
                        || (modificationType == LscModificationType.CHANGE_ID
                                && (abstractSynchronize.nomodrdn || abstractSynchronize.noupdate))) {
                    abstractSynchronize.logShouldAction(lm, syncName);
                    return true;
                }

            } else {
                return true;
            }

            // if we got here, we have a modification to apply - let's do it!
            if (task.getDestinationService().apply(lm)) {
                counter.incrementCountCompleted();
                abstractSynchronize.logAction(lm, id, syncName);
                return true;
            } else {
                counter.incrementCountError();
                abstractSynchronize.logActionError(lm, (id != null ? id.getValue() : entry.getMainIdentifier()),
                        new Exception("Technical problem while applying modifications to the destination"));
                return false;
            }
        } catch (RuntimeException e) {
            counter.incrementCountError();
            abstractSynchronize.logActionError(lm,
                    (id != null ? id.getValue() : (entry != null ? entry.getMainIdentifier() : e.toString())), e);

            if (e.getCause() instanceof LscServiceCommunicationException) {
                AbstractSynchronize.LOGGER.error("Connection lost! Aborting.");
            }
            return false;
        } catch (Exception e) {
            counter.incrementCountError();
            abstractSynchronize.logActionError(lm, (id != null ? id.getValue() : entry.getMainIdentifier()), e);
            return false;
        }
    }

    public String getSyncName() {
        return syncName;
    }

    public InfoCounter getCounter() {
        return counter;
    }

    public AbstractSynchronize getAbstractSynchronize() {
        return abstractSynchronize;
    }

    public Entry<String, LscDatasets> getId() {
        return id;
    }

}

/**
 * This object is storing counters across all tasks Update methods are specified
 * as synchronized to avoid loosing counts of operations
 * 
 * @author Sebastien Bahloul <seb@lsc-project.org>
 */
class InfoCounter {

    private int countAll = 0;
    private int countError = 0;
    private int countModifiable = 0;
    private int countCompleted = 0;

    public synchronized void incrementCountAll() {
        countAll++;
    }

    public synchronized void incrementCountError() {
        countError++;
    }

    public synchronized void incrementCountModifiable() {
        countModifiable++;
    }

    public synchronized void incrementCountCompleted() {
        countCompleted++;
    }

    /**
     * Return the count of all objects concerned by synchronization It does not
     * include objects in data source that are not selected by requests or
     * filters, but it includes any of the objects retrieved from the data
     * source
     * 
     * @return the count of all objects taken from the data source
     */
    public synchronized int getCountAll() {
        return countAll;
    }

    /**
     * Return the count of all objects that have encountered an error while
     * synchronizing, either for a technical or for a functional reason
     * 
     * @return the number of objects in error
     */
    public synchronized int getCountError() {
        return countError;
    }

    /**
     * Return the count of all objects that should be modify
     * 
     * @return the count of all updates to do
     */
    public synchronized int getCountModifiable() {
        return countModifiable;
    }

    /**
     * Return the count of all objects that have been embraced in a data
     * modification successfully
     * 
     * @return the count of all successful updates
     */
    public synchronized int getCountCompleted() {
        return countCompleted;
    }
}