org.ow2.proactive.scheduler.task.data.TaskProActiveDataspaces.java Source code

Java tutorial

Introduction

Here is the source code for org.ow2.proactive.scheduler.task.data.TaskProActiveDataspaces.java

Source

/*
 * ProActive Parallel Suite(TM):
 * The Open Source library for parallel and distributed
 * Workflows & Scheduling, Orchestration, Cloud Automation
 * and Big Data Analysis on Enterprise Grids & Clouds.
 *
 * Copyright (c) 2007 - 2017 ActiveEon
 * Contact: contact@activeeon.com
 *
 * This library is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation: version 3 of
 * the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * If needed, contact us to obtain a release under GPL Version 2 or 3
 * or a different license than the AGPL.
 */
package org.ow2.proactive.scheduler.task.data;

import static com.google.common.base.Throwables.getStackTraceAsString;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.locks.ReentrantLock;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.objectweb.proactive.api.PAActiveObject;
import org.objectweb.proactive.core.node.Node;
import org.objectweb.proactive.extensions.dataspaces.Utils;
import org.objectweb.proactive.extensions.dataspaces.api.DataSpacesFileObject;
import org.objectweb.proactive.extensions.dataspaces.api.FileSelector;
import org.objectweb.proactive.extensions.dataspaces.api.FileType;
import org.objectweb.proactive.extensions.dataspaces.api.PADataSpaces;
import org.objectweb.proactive.extensions.dataspaces.core.DataSpacesNodes;
import org.objectweb.proactive.extensions.dataspaces.core.InputOutputSpaceConfiguration;
import org.objectweb.proactive.extensions.dataspaces.core.SpaceInstanceInfo;
import org.objectweb.proactive.extensions.dataspaces.core.naming.NamingService;
import org.objectweb.proactive.extensions.dataspaces.exceptions.FileSystemException;
import org.objectweb.proactive.extensions.dataspaces.exceptions.SpaceAlreadyRegisteredException;
import org.objectweb.proactive.utils.NamedThreadFactory;
import org.objectweb.proactive.utils.OperatingSystem;
import org.objectweb.proactive.utils.StackTraceUtil;
import org.ow2.proactive.resourcemanager.nodesource.dataspace.DataSpaceNodeConfigurationAgent;
import org.ow2.proactive.scheduler.common.SchedulerConstants;
import org.ow2.proactive.scheduler.common.task.TaskId;
import org.ow2.proactive.scheduler.common.task.dataspaces.InputSelector;
import org.ow2.proactive.scheduler.common.task.dataspaces.OutputSelector;

public class TaskProActiveDataspaces implements TaskDataspaces {

    private static final transient Logger logger = Logger.getLogger(TaskProActiveDataspaces.class);

    public static final String PA_NODE_DATASPACE_FILE_TRANSFER_THREAD_POOL_SIZE = "pa.node.dataspace.filetransfer.threadpoolsize";

    public static final String PA_NODE_DATASPACE_CREATE_FOLDER_HIERARCHY_SEQUENTIALLY = "pa.node.dataspace.create_folder_hierarchy_sequentially";

    private transient DataSpacesFileObject SCRATCH;

    private transient DataSpacesFileObject CACHE;

    private transient DataSpacesFileObject INPUT;

    private transient DataSpacesFileObject OUTPUT;

    private transient DataSpacesFileObject GLOBAL;

    private transient DataSpacesFileObject USER;

    private TaskId taskId;

    private transient NamingService namingService;

    private boolean runAsUser;

    private boolean linuxOS;

    private static transient ReentrantLock cacheTransferLock = new ReentrantLock();

    private SpaceInstanceInfo cacheSpaceInstanceInfo;

    private transient StringBuffer clientLogs = new StringBuffer();

    private transient ExecutorService executorTransfer = Executors
            .newFixedThreadPool(getFileTransferThreadPoolSize(), new NamedThreadFactory("FileTransferThreadPool"));

    /**
     * Mainly for testing purposes
     */
    TaskProActiveDataspaces() {

    }

    public TaskProActiveDataspaces(TaskId taskId, NamingService namingService, boolean isRunAsUser)
            throws Exception {
        this.taskId = taskId;
        this.namingService = namingService;
        this.runAsUser = isRunAsUser;
        this.linuxOS = OperatingSystem.getOperatingSystem() == OperatingSystem.unix;
        initDataSpaces();
    }

    protected int getFileTransferThreadPoolSize() {
        String sizeAsString = System.getProperty(PA_NODE_DATASPACE_FILE_TRANSFER_THREAD_POOL_SIZE);

        int result = Runtime.getRuntime().availableProcessors() * 2;

        if (sizeAsString != null) {
            try {
                result = Integer.parseInt(sizeAsString);
            } catch (NumberFormatException e) {
                // default value will be used
                String message = "Invalid value set for property '"
                        + PA_NODE_DATASPACE_FILE_TRANSFER_THREAD_POOL_SIZE + "': " + sizeAsString;
                logger.warn(message);
                logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
            }
        }

        logger.info("Thread pool size for file transfer is " + result);

        return result;
    }

    private DataSpacesFileObject createTaskIdFolder(DataSpacesFileObject space, String spaceName) {
        if (space != null) {
            String realURI = space.getRealURI();
            // Look for the TASKID pattern at the end of the dataspace URI
            if (realURI.contains(SchedulerConstants.TASKID_DIR_DEFAULT_NAME)) {
                // resolve the taskid subfolder
                DataSpacesFileObject tidOutput;
                try {
                    tidOutput = space.resolveFile(taskId.toString());
                    // create this subfolder
                    tidOutput.createFolder();
                } catch (FileSystemException e) {
                    logger.info("Error when creating the TASKID folder in " + realURI, e);
                    logger.info(spaceName + " space is disabled");
                    return null;
                }
                // assign it to the space
                space = tidOutput;
                logger.info(SchedulerConstants.TASKID_DIR_DEFAULT_NAME + " pattern found, changed " + spaceName
                        + " space to : " + space.getRealURI());
            }
        }
        return space;
    }

    private DataSpacesFileObject resolveToExisting(DataSpacesFileObject space, String spaceName, boolean input) {
        if (space == null) {
            logger.info(spaceName + " space is disabled");
            return null;
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Resolving " + spaceName + ": " + space.getAllRealURIs());
        }
        // ensure that the remote folder exists (in case we didn't replace any pattern)
        try {
            space = space.ensureExistingOrSwitch(!input);
        } catch (Exception e) {
            logger.info("Error occurred when switching to alternate space root", e);
            logger.info(spaceName + " space is disabled");
            return null;
        }
        if (space == null) {
            logger.info("No existing " + spaceName + " space found");
            logger.info(spaceName + " space is disabled");
        } else {
            logger.debug(spaceName + " space is " + space.getRealURI());
            logger.debug("(other available urls for " + spaceName + " space are " + space.getAllRealURIs() + " )");
        }
        return space;
    }

    private void initDataSpaces() throws Exception {
        // configure node for application
        String appId = taskId.toString();

        // prepare scratch, input, output

        Node node = PAActiveObject.getNode();
        logger.info("Configuring dataspaces for app " + appId + " on " + node.getNodeInformation().getName());
        DataSpacesNodes.configureApplication(node, appId, namingService);

        SCRATCH = PADataSpaces.resolveScratchForAO();
        logger.info("SCRATCH space is " + SCRATCH.getRealURI());

        // Set the scratch folder writable for everyone
        if (!SCRATCH.setWritable(true, false)) {
            logger.warn("Missing permission to change write permissions to " + getScratchFolder());
        }

        InputOutputSpaceConfiguration cacheConfiguration = DataSpaceNodeConfigurationAgent
                .getCacheSpaceConfiguration();

        if (cacheConfiguration != null) {
            final String cacheName = cacheConfiguration.getName();

            cacheSpaceInstanceInfo = new SpaceInstanceInfo(appId, cacheConfiguration);
            try {
                namingService.register(cacheSpaceInstanceInfo);
            } catch (SpaceAlreadyRegisteredException e) {
                // this is a rare case where the cache space has already been registered for the same task and there was a node failure.
                namingService.unregister(cacheSpaceInstanceInfo.getMountingPoint());
                namingService.register(cacheSpaceInstanceInfo);
            }

            CACHE = initDataSpace(new Callable<DataSpacesFileObject>() {
                @Override
                public DataSpacesFileObject call() throws Exception {
                    return PADataSpaces.resolveOutput(cacheName);
                }
            }, "CACHE", false);
        } else {
            logger.error("No Cache space configuration found, cache space is disabled.");
        }

        INPUT = initDataSpace(new Callable<DataSpacesFileObject>() {
            @Override
            public DataSpacesFileObject call() throws Exception {
                return PADataSpaces.resolveDefaultInput();
            }
        }, "INPUT", true);

        OUTPUT = initDataSpace(new Callable<DataSpacesFileObject>() {
            @Override
            public DataSpacesFileObject call() throws Exception {
                return PADataSpaces.resolveDefaultOutput();
            }
        }, "OUTPUT", false);

        GLOBAL = initDataSpace(new Callable<DataSpacesFileObject>() {
            @Override
            public DataSpacesFileObject call() throws Exception {
                return PADataSpaces.resolveOutput(SchedulerConstants.GLOBALSPACE_NAME);
            }
        }, "GLOBAL", false);

        USER = initDataSpace(new Callable<DataSpacesFileObject>() {
            @Override
            public DataSpacesFileObject call() throws Exception {
                return PADataSpaces.resolveOutput(SchedulerConstants.USERSPACE_NAME);
            }
        }, "USER", false);

    }

    private DataSpacesFileObject initDataSpace(Callable<DataSpacesFileObject> dataSpaceBuilder,
            String dataSpaceName, boolean input) throws Exception {
        try {
            DataSpacesFileObject result = dataSpaceBuilder.call();
            result = resolveToExisting(result, dataSpaceName, input);
            result = createTaskIdFolder(result, dataSpaceName);
            return result;
        } catch (FileSystemException fse) {
            String message = dataSpaceName + " space is disabled";
            logger.warn(message, fse);
            logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
            logDataspacesStatus(getStackTraceAsString(fse), DataspacesStatusLevel.WARNING);
        }
        return null;
    }

    private static String convertDataSpaceURIToFileIfPossible(String dataspaceURI, boolean errorIfNotFile) {
        URI foUri;
        try {
            foUri = new URI(dataspaceURI);
        } catch (URISyntaxException e) {
            throw new IllegalStateException(e);
        }
        String answer;
        if (foUri.getScheme() == null || foUri.getScheme().equals("file")) {
            answer = (new File(foUri)).getAbsolutePath();
        } else {
            if (errorIfNotFile) {
                throw new IllegalStateException(
                        "Space " + dataspaceURI + " is not accessible via the file system.");
            }
            answer = foUri.toString();
        }
        return answer;
    }

    @Override
    public File getScratchFolder() {
        if (SCRATCH == null) {
            throw new IllegalStateException("SCRATCH space not mounted");
        }

        return new File(convertDataSpaceURIToFileIfPossible(SCRATCH.getRealURI(), true));
    }

    @Override
    public String getScratchURI() {
        if (SCRATCH == null) {
            throw new IllegalStateException("SCRATCH space not mounted");
        }
        return convertDataSpaceURIToFileIfPossible(SCRATCH.getRealURI(), true);
    }

    @Override
    public String getCacheURI() {
        if (CACHE == null) {
            return "";
        }
        return convertDataSpaceURIToFileIfPossible(CACHE.getRealURI(), false);
    }

    @Override
    public String getInputURI() {
        if (INPUT == null) {
            return "";
        }
        return convertDataSpaceURIToFileIfPossible(INPUT.getRealURI(), false);
    }

    @Override
    public String getOutputURI() {
        if (OUTPUT == null) {
            return "";
        }
        return convertDataSpaceURIToFileIfPossible(OUTPUT.getRealURI(), false);
    }

    @Override
    public String getUserURI() {
        if (USER == null) {
            return "";
        }
        return convertDataSpaceURIToFileIfPossible(USER.getRealURI(), false);
    }

    @Override
    public String getGlobalURI() {
        if (GLOBAL == null) {
            return "";
        }
        return convertDataSpaceURIToFileIfPossible(GLOBAL.getRealURI(), false);
    }

    private enum DataspacesStatusLevel {
        ERROR, WARNING, INFO
    }

    private void logDataspacesStatus(String message, DataspacesStatusLevel level) {
        final String eol = System.lineSeparator();
        final boolean hasEol = message.endsWith(eol);

        if (level == DataspacesStatusLevel.ERROR) {
            this.clientLogs.append("[DATASPACES-ERROR] ").append(message).append(hasEol ? "" : eol);
        } else if (level == DataspacesStatusLevel.WARNING) {
            this.clientLogs.append("[DATASPACES-WARNING] ").append(message).append(hasEol ? "" : eol);
        } else if (level == DataspacesStatusLevel.INFO) {
            this.clientLogs.append("[DATASPACES-INFO] ").append(message).append(hasEol ? "" : eol);
        }
    }

    private boolean checkInputSpaceConfigured(DataSpacesFileObject space, String spaceName, InputSelector is) {
        if (space == null) {
            String message = "Job " + spaceName
                    + " space is not defined or not properly configured while input files are specified: ";

            logger.error(message);
            logDataspacesStatus(message, DataspacesStatusLevel.ERROR);

            logger.error("--> " + is);
            logDataspacesStatus("--> " + is, DataspacesStatusLevel.ERROR);

            return false;
        }

        return true;
    }

    @Override
    public void copyInputDataToScratch(List<InputSelector> inputSelectors)
            throws FileSystemException, InterruptedException {
        try {
            if (inputSelectors == null) {
                logger.debug("Input selector is empty, no file to copy");
                return;
            }

            ArrayList<DataSpacesFileObject> inputSpaceFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> outputSpaceFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> globalSpaceFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> userSpaceFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> inputSpaceCacheFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> outputSpaceCacheFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> globalSpaceCacheFiles = new ArrayList<>();
            ArrayList<DataSpacesFileObject> userSpaceCacheFiles = new ArrayList<>();

            FileSystemException exception = findFilesToCopyFromInput(inputSelectors, inputSpaceFiles,
                    outputSpaceFiles, globalSpaceFiles, userSpaceFiles, inputSpaceCacheFiles, outputSpaceCacheFiles,
                    globalSpaceCacheFiles, userSpaceCacheFiles);

            if (exception != null) {
                throw exception;
            }

            String inputSpaceUri = virtualResolve(INPUT);
            String outputSpaceUri = virtualResolve(OUTPUT);
            String globalSpaceUri = virtualResolve(GLOBAL);
            String userSpaceUri = virtualResolve(USER);

            boolean cacheTransferPresent = !inputSpaceCacheFiles.isEmpty() || !outputSpaceCacheFiles.isEmpty()
                    || !globalSpaceCacheFiles.isEmpty() || !userSpaceCacheFiles.isEmpty();
            if (cacheTransferPresent && CACHE != null) {
                cacheTransferLock.lockInterruptibly();
                try {

                    Map<String, DataSpacesFileObject> filesToCopyToCache = createFolderHierarchySequentially(CACHE,
                            inputSpaceUri, inputSpaceCacheFiles, outputSpaceUri, outputSpaceCacheFiles,
                            globalSpaceUri, globalSpaceCacheFiles, userSpaceUri, userSpaceCacheFiles);

                    List<Future<Boolean>> transferFuturesCache = doCopyInputDataToSpace(CACHE, filesToCopyToCache);

                    handleResults(transferFuturesCache);
                } finally {
                    if (cacheTransferPresent) {
                        cacheTransferLock.unlock();
                    }
                }
            } else if (cacheTransferPresent) {
                logDataspacesStatus(
                        "CACHE dataspace is not available while file transfers to cache were required. Check the Node logs for errors.",
                        DataspacesStatusLevel.ERROR);
            }

            Map<String, DataSpacesFileObject> filesToCopyToScratch = createFolderHierarchySequentially(SCRATCH,
                    inputSpaceUri, inputSpaceFiles, outputSpaceUri, outputSpaceFiles, globalSpaceUri,
                    globalSpaceFiles, userSpaceUri, userSpaceFiles);

            List<Future<Boolean>> transferFuturesScratch = doCopyInputDataToSpace(SCRATCH, filesToCopyToScratch);

            handleResults(transferFuturesScratch);

        } finally {
            // display dataspaces error and warns if any
            displayDataspacesStatus();
        }
    }

    private Map<String, DataSpacesFileObject> createFolderHierarchySequentially(DataSpacesFileObject space,
            String inputSpaceUri, ArrayList<DataSpacesFileObject> inputSpaceFiles, String outputSpaceUri,
            ArrayList<DataSpacesFileObject> outputSpaceFiles, String globalSpaceUri,
            ArrayList<DataSpacesFileObject> globalSpaceFiles, String userSpaceUri,
            ArrayList<DataSpacesFileObject> userSpaceFiles) throws FileSystemException {

        // This map will contain the files that have to be copied.
        Map<String, DataSpacesFileObject> result = new HashMap<>(
                outputSpaceFiles.size() + globalSpaceFiles.size() + userSpaceFiles.size() + inputSpaceFiles.size());

        // Since multiple spaces are involved, it is possible to have
        // a file with the same name present in each space. Consequently,
        // the one to copy has to be selected since there is only a single
        // possible destination, the scratch space.
        // The reverse order of the next calls gives the precedence order
        // of the spaces when the previous situation occurs:
        // output, input, user and global space
        // Precedence is given to the more specific files
        createFolderHierarchySequentially(space, globalSpaceUri, globalSpaceFiles, result);
        createFolderHierarchySequentially(space, userSpaceUri, userSpaceFiles, result);
        createFolderHierarchySequentially(space, inputSpaceUri, inputSpaceFiles, result);
        createFolderHierarchySequentially(space, outputSpaceUri, outputSpaceFiles, result);

        return result;
    }

    /*
     * Create the folder hierarchy and select the files to copy
     * from the specified list of FileObjects.
     */
    protected void createFolderHierarchySequentially(DataSpacesFileObject destination, String spaceUri,
            List<DataSpacesFileObject> spaceFiles, Map<String, DataSpacesFileObject> filesToCopy)
            throws FileSystemException {

        boolean isDebugEnabled = logger.isDebugEnabled();
        boolean isFolderHierarchyCreationEnabled = isCreateFolderHierarchySequentiallyEnabled();
        long startTime = 0;

        if (isDebugEnabled) {
            startTime = System.currentTimeMillis();
        }

        for (DataSpacesFileObject fileObject : spaceFiles) {
            String relativePath = relativize(spaceUri, fileObject);

            if (isFolderHierarchyCreationEnabled) {
                try {
                    DataSpacesFileObject target = destination.resolveFile(relativePath);
                    createFolderHierarchy(isDebugEnabled, fileObject, target);
                } catch (FileSystemException e) {
                    String message = "Could not create folder hierarchy for " + relativePath + " on "
                            + destination.getRealURI();
                    logger.warn(message);
                    logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
                }
            }

            DataSpacesFileObject oldFileObject = filesToCopy.put(relativePath, fileObject);
            if (oldFileObject != null) {
                String message = fileObject.getRealURI() + " will be copied instead of "
                        + oldFileObject.getRealURI() + ".\n "
                        + "Precedence order is output space, input space, user space, global space.";
                logger.warn(message);
                logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
            }
        }

        if (isDebugEnabled) {
            long timeToCreateHierarchySequentially = System.currentTimeMillis() - startTime;
            logger.debug("Executing TaskProActiveDataspaces#createFolderHierarchySequentially has taken "
                    + timeToCreateHierarchySequentially + " ms");
        }
    }

    protected void createFolderHierarchy(boolean isDebugEnabled, DataSpacesFileObject fileObject,
            DataSpacesFileObject target) throws FileSystemException {

        FileType fileObjectType = fileObject.getType();

        if (FileType.FOLDER.equals(fileObjectType)) {
            if (isDebugEnabled) {
                logger.debug("Creating folder " + target.getRealURI());
            }
            if (!target.exists()) {
                target.createFolder();
                setFolderRightsForRunAsUserMode(target);
            }

        } else if (FileType.FILE.equals(fileObjectType)) {
            DataSpacesFileObject parent = target.getParent();

            if (isDebugEnabled) {
                logger.debug("Creating folder " + parent.getRealURI());
            }
            if (!parent.exists()) {
                parent.createFolder();
                setFolderRightsForRunAsUserMode(parent);
            }
        }
    }

    protected boolean isCreateFolderHierarchySequentiallyEnabled() {
        String property = System.getProperty(PA_NODE_DATASPACE_CREATE_FOLDER_HIERARCHY_SEQUENTIALLY);

        return property == null || "true".equalsIgnoreCase(property);
    }

    /**
     * Sets open file permissions for files copied in RunAsMe mode
     */
    private void setFileRightsForRunAsUserMode(DataSpacesFileObject object) throws FileSystemException {
        if (runAsUser) {
            setRWPermission(object);
        }
    }

    private void setRWPermission(DataSpacesFileObject object) throws FileSystemException {
        object.setReadable(true, false);
        object.setWritable(true, false);
    }

    /**
     * Sets open file permissions for folder copied in RunAsMe mode.
     * The method will set as well recursively the permissions on the parents folder.
     */
    private void setFolderRightsForRunAsUserMode(DataSpacesFileObject object) throws FileSystemException {
        if (runAsUser) {
            setRWPermission(object);
            if (linuxOS) {
                object.setExecutable(true, false);
            }
            DataSpacesFileObject parentObject = null;
            try {
                parentObject = object.getParent();
            } catch (Exception ignored) {
                // in case getParent throws an exception instead of null, we prefer to ignore it and not propagate the permissions further.
            }
            if (parentObject != null) {
                setFolderRightsForRunAsUserMode(parentObject);
            }

        }
    }

    protected void handleResults(List<Future<Boolean>> transferFutures) throws FileSystemException {

        StringBuilder message = new StringBuilder();
        String nl = System.lineSeparator();

        for (Future<Boolean> future : transferFutures) {
            try {
                future.get();
            } catch (InterruptedException | ExecutionException e) {
                logger.error("Exception while fetching dataspace transfer result ", e);
                message.append(StackTraceUtil.getStackTrace(e)).append(nl);
            }
        }

        if (message.length() > 0) {
            throw new FileSystemException(
                    "Exception(s) occurred when transferring input file: " + nl + message.toString());
        }
    }

    protected String relativize(String inputSpaceUri, DataSpacesFileObject fileObject) {
        return fileObject.getVirtualURI().replaceFirst(inputSpaceUri + "/?", "");
    }

    private String virtualResolve(DataSpacesFileObject dataSpacesFileObject) {
        if (dataSpacesFileObject == null) {
            return "";
        } else {
            return dataSpacesFileObject.getVirtualURI();
        }
    }

    private FileSystemException findFilesToCopyFromInput(List<InputSelector> inputSelectors,
            ArrayList<DataSpacesFileObject> inResults, ArrayList<DataSpacesFileObject> outResults,
            ArrayList<DataSpacesFileObject> globResults, ArrayList<DataSpacesFileObject> userResults,
            ArrayList<DataSpacesFileObject> inResultsCache, ArrayList<DataSpacesFileObject> outResultsCache,
            ArrayList<DataSpacesFileObject> globResultsCache, ArrayList<DataSpacesFileObject> userResultsCache) {

        FileSystemException toBeThrown = null;

        for (InputSelector is : inputSelectors) {
            org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector selector = new org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector();
            selector.setIncludes(is.getInputFiles().getIncludes());
            selector.setExcludes(is.getInputFiles().getExcludes());

            logger.debug("Selector used is " + selector);

            switch (is.getMode()) {
            case TransferFromInputSpace:
                toBeThrown = findFilesToCopyFromInput(INPUT, "INPUT", is, selector, inResults);
                break;
            case TransferFromOutputSpace:
                toBeThrown = findFilesToCopyFromInput(OUTPUT, "OUTPUT", is, selector, outResults);
                break;
            case TransferFromGlobalSpace:
                toBeThrown = findFilesToCopyFromInput(GLOBAL, "GLOBAL", is, selector, globResults);
                break;
            case TransferFromUserSpace:
                toBeThrown = findFilesToCopyFromInput(USER, "USER", is, selector, userResults);
                break;
            case CacheFromInputSpace:
                toBeThrown = findFilesToCopyFromInput(INPUT, "INPUT", is, selector, inResultsCache);
                break;
            case CacheFromOutputSpace:
                toBeThrown = findFilesToCopyFromInput(OUTPUT, "OUTPUT", is, selector, outResultsCache);
                break;
            case CacheFromGlobalSpace:
                toBeThrown = findFilesToCopyFromInput(GLOBAL, "GLOBAL", is, selector, globResultsCache);
                break;
            case CacheFromUserSpace:
                toBeThrown = findFilesToCopyFromInput(USER, "USER", is, selector, userResultsCache);
            case none:
                //do nothing
                break;
            }
        }

        return toBeThrown;
    }

    private List<Future<Boolean>> doCopyInputDataToSpace(DataSpacesFileObject space,
            Map<String, DataSpacesFileObject> filesToCopy) {

        List<Future<Boolean>> transferFutures = new ArrayList<>(filesToCopy.size());

        for (Map.Entry<String, DataSpacesFileObject> entry : filesToCopy.entrySet()) {
            transferFutures.add(parallelFileCopy(entry.getValue(), space, entry.getKey(), true));
        }

        return transferFutures;
    }

    private Future<Boolean> parallelFileCopy(final DataSpacesFileObject source,
            final DataSpacesFileObject destinationBase, final String destinationRelativeToBase,
            final boolean isInputFile) {

        logger.debug("------------ resolving " + destinationRelativeToBase);

        return executorTransfer.submit(new Callable<Boolean>() {
            @Override
            public Boolean call() throws FileSystemException {

                DataSpacesFileObject target = destinationBase.resolveFile(destinationRelativeToBase);
                if (!target.exists()) {
                    logger.info("Copying " + source.getRealURI() + " to " + destinationBase.getRealURI() + "/"
                            + destinationRelativeToBase);
                    target.copyFrom(source, FileSelector.SELECT_SELF);
                } else if (source.getContent().getLastModifiedTime() > target.getContent().getLastModifiedTime()) {
                    logger.info("Copying " + source.getRealURI() + " to " + destinationBase.getRealURI() + "/"
                            + destinationRelativeToBase + " (newer version)");
                    target.copyFrom(source, FileSelector.SELECT_SELF);
                } else {
                    logger.info("Destination file " + target.getRealURI() + " is already present and newer.");
                }

                target.refresh();

                if (!target.exists()) {
                    String message = "There was a problem during the copy of " + source.getRealURI() + " to "
                            + target.getRealURI() + ". File not present after copy.";
                    logger.error(message);
                    logDataspacesStatus(message, DataspacesStatusLevel.ERROR);
                } else {
                    if (isInputFile) {
                        setFileRightsForRunAsUserMode(target);
                    }
                }
                return true;
            }
        });
    }

    private FileSystemException findFilesToCopyFromInput(DataSpacesFileObject space, String spaceName,
            InputSelector inputSelector,
            org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector selector,
            List<DataSpacesFileObject> results) {

        if (!checkInputSpaceConfigured(space, spaceName, inputSelector)) {
            return null;
        }

        try {
            // A desynchronization has been noticed when multiple dataspaces are mounted on the same folder
            // The call to refresh ensures that the content of the dataspace cache is resynchronized with the disk
            // before the transfer
            space.refresh();

            int oldSize = results.size();

            Utils.findFiles(space, selector, results);

            if (results.size() == oldSize) {
                // we detected that there was no new file in the list
                String message = "No file is transferred from " + spaceName + " space at " + space.getRealURI()
                        + "  for selector " + inputSelector;

                logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
                logger.warn(message);
            }
        } catch (FileSystemException e) {
            logger.warn("Error occurred while transferring files", e);

            String message = "Could not contact " + spaceName + " space at " + space.getRealURI()
                    + ". An error occurred while resolving selector " + inputSelector;

            logDataspacesStatus(message, DataspacesStatusLevel.ERROR);
            logDataspacesStatus(getStackTraceAsString(e), DataspacesStatusLevel.ERROR);

            logger.error(message, e);

            return new FileSystemException(message);
        } catch (NullPointerException e) {
            // nothing to do
            return null;
        }

        return null;
    }

    @Override
    public void copyScratchDataToOutput(List<OutputSelector> outputSelectors) throws FileSystemException {
        try {
            if (outputSelectors == null) {
                logger.debug("Output selector is empty, no file to copy");
                return;
            }

            checkOutputSpacesConfigured(outputSelectors);

            ArrayList<DataSpacesFileObject> results = new ArrayList<>();
            FileSystemException toBeThrown = null;

            for (OutputSelector os : outputSelectors) {
                org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector selector = new org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector();
                selector.setIncludes(os.getOutputFiles().getIncludes());
                selector.setExcludes(os.getOutputFiles().getExcludes());

                switch (os.getMode()) {
                case TransferToOutputSpace:
                    if (OUTPUT != null) {
                        toBeThrown = copyScratchDataToOutput(OUTPUT, "OUTPUT", os, selector, results);
                    }
                    break;
                case TransferToGlobalSpace:
                    if (GLOBAL != null) {
                        toBeThrown = copyScratchDataToOutput(GLOBAL, "GLOBAL", os, selector, results);
                    }
                    break;
                case TransferToUserSpace:
                    if (USER != null) {
                        toBeThrown = copyScratchDataToOutput(USER, "USER", os, selector, results);
                        break;
                    }
                case none:
                    break;
                }

                results.clear();
            }

            if (toBeThrown != null) {
                throw toBeThrown;
            }

        } finally {
            // display dataspaces error and warns if any
            displayDataspacesStatus();
        }
    }

    private void checkOutputSpacesConfigured(List<OutputSelector> outputSelectors) {
        // Check that output spaces are properly configured, A message is put in the user log output if not
        for (OutputSelector os1 : outputSelectors) {
            switch (os1.getMode()) {
            case TransferToOutputSpace:
                checkOuputSpaceConfigured(OUTPUT, "OUTPUT", os1);
                break;
            case TransferToGlobalSpace:
                checkOuputSpaceConfigured(GLOBAL, "GLOBAL", os1);
                break;
            case TransferToUserSpace:
                checkOuputSpaceConfigured(USER, "USER", os1);
                break;
            }
        }
    }

    private boolean checkOuputSpaceConfigured(DataSpacesFileObject space, String spaceName, OutputSelector os) {
        if (space == null) {
            String message = "Job " + spaceName + " space is not defined or not properly configured, "
                    + "while output files are specified :";

            logger.debug(message);

            logDataspacesStatus(message, DataspacesStatusLevel.ERROR);
            logDataspacesStatus("--> " + os, DataspacesStatusLevel.ERROR);

            return false;
        }

        return true;
    }

    @Override
    public void close() {
        if (!executorTransfer.shutdownNow().isEmpty()) {
            String message = "Remaining tasks to execute while closing thread pool used for data transfer";
            logger.error(message);
            logDataspacesStatus(message, DataspacesStatusLevel.ERROR);
        }

        if (CACHE != null) {
            try {
                logger.info("Unregistering cache space : " + cacheSpaceInstanceInfo.getMountingPoint());
                namingService.unregister(cacheSpaceInstanceInfo.getMountingPoint());
            } catch (Exception e) {
                logger.warn("Error occurred when unregistering Cache space", e);
            }
        }

        cleanScratchSpace();
    }

    private void cleanScratchSpace() {
        try {
            File folder = getScratchFolder();
            FileUtils.deleteQuietly(folder);
        } catch (Exception ignored) {
        }
    }

    private FileSystemException copyScratchDataToOutput(DataSpacesFileObject dataspace, String spaceName,
            OutputSelector outputSelector,
            org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector selector,
            List<DataSpacesFileObject> results) {
        try {
            int sizeBeforeHandlingOutput = results.size();

            handleOutput(dataspace, selector, results);

            if (results.size() == sizeBeforeHandlingOutput) {
                String message = "No file is transferred to " + spaceName + " space at " + dataspace.getRealURI()
                        + " for selector " + outputSelector;

                logDataspacesStatus(message, DataspacesStatusLevel.WARNING);
                logger.warn(message);
            }
        } catch (FileSystemException fse) {
            String message = "Error while transferring to " + spaceName + " space at " + dataspace.getRealURI()
                    + " for selector " + outputSelector;

            logDataspacesStatus(message, DataspacesStatusLevel.ERROR);
            logDataspacesStatus(getStackTraceAsString(fse), DataspacesStatusLevel.ERROR);
            logger.error(message, fse);
            return fse;
        }

        return null;
    }

    /**
     * Display the content of the dataspaces status buffer on stderr if non empty.
     */
    private void displayDataspacesStatus() {
        if (this.clientLogs.length() != 0) {
            logger.warn(clientLogs);
            this.clientLogs = new StringBuffer();
        }
    }

    private void handleOutput(final DataSpacesFileObject dataspaceDestination,
            org.objectweb.proactive.extensions.dataspaces.vfs.selector.FileSelector selector,
            List<DataSpacesFileObject> results) throws FileSystemException {

        Utils.findFiles(SCRATCH, selector, results);

        if (logger.isDebugEnabled()) {
            if (results == null || results.size() == 0) {
                logger.debug("No file found to copy from LOCAL space to OUTPUT space");
            } else {
                logger.debug("Files that will be copied from LOCAL space to OUTPUT space :");
            }
        }

        String base = SCRATCH.getVirtualURI();

        Map<String, DataSpacesFileObject> filesToCopy = new HashMap<>(results.size());

        createFolderHierarchySequentially(dataspaceDestination, base, results, filesToCopy);

        ArrayList<Future<Boolean>> transferFutures = new ArrayList<>(results.size());

        for (Map.Entry<String, DataSpacesFileObject> entry : filesToCopy.entrySet()) {
            transferFutures.add(parallelFileCopy(entry.getValue(), dataspaceDestination, entry.getKey(), false));
        }

        handleResults(transferFutures);
    }

}