uk.ac.ucl.excites.sapelli.collector.load.ProjectLoader.java Source code

Java tutorial

Introduction

Here is the source code for uk.ac.ucl.excites.sapelli.collector.load.ProjectLoader.java

Source

/**
 * Sapelli data collection platform: http://sapelli.org
 * 
 * Copyright 2012-2016 University College London - ExCiteS group
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package uk.ac.ucl.excites.sapelli.collector.load;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FileUtils;

import uk.ac.ucl.excites.sapelli.collector.io.FileStorageProvider;
import uk.ac.ucl.excites.sapelli.collector.load.parse.ProjectParser;
import uk.ac.ucl.excites.sapelli.collector.load.process.PostProcessTask;
import uk.ac.ucl.excites.sapelli.collector.load.process.PostProcessor;
import uk.ac.ucl.excites.sapelli.collector.model.Project;
import uk.ac.ucl.excites.sapelli.shared.io.FileHelpers;
import uk.ac.ucl.excites.sapelli.shared.io.FileStorageException;
import uk.ac.ucl.excites.sapelli.shared.io.Unzipper;
import uk.ac.ucl.excites.sapelli.shared.util.WarningKeeper;
import uk.ac.ucl.excites.sapelli.storage.model.Schema;

/**
 * Class with methods to load (or just parse) Sapelli projects from .sapelli/.excites/.sap files (which are actually just renamed ZIP files).
 * 
 * @author mstevens, Michalis Vitos
 */
public class ProjectLoader implements WarningKeeper {

    // STATICS -----------------------------------------------------------
    static public final String[] SAPELLI_FILE_EXTENSIONS = { "sap", "sapelli", "excites", "zip" };
    static public final String PROJECT_FILE = "PROJECT.xml";

    /**
     * Checks if the given file has a support sapelli file extension
     * 
     * @param file
     * @return
     */
    static public boolean HasSapelliFileExtension(File file) {
        String fileExt = FileHelpers.getFileExtension(file);
        for (String sapExt : ProjectLoader.SAPELLI_FILE_EXTENSIONS)
            if (sapExt.equalsIgnoreCase(fileExt))
                return true;
        return false;
    }

    /**
      * Parses the {@value #PROJECT_FILE} file in the folder at the given path to produce a {@link Project} instance.
     * No warnings are generated and no exceptions thrown.
     * Use this for projects that have been successfully parsed before. Otherwise it is advisable to use
     * the non-static {@link #load(File)} or {@link #loadParseOnly(File)} methods instead.
     * 
     * @param folderPath path to folder in which the PROJECT.xml file resides
     * @return a {@link Project} instance or {@code null} in case something went wrong
     */
    static public Project ParseProjectXMLInFolder(String folderPath) {
        return ParseProjectXMLInFolder(new File(folderPath));
    }

    /**
      * Parses the {@value #PROJECT_FILE} file in the given folder to produce a {@link Project} instance.
     * No warnings are generated and no exceptions thrown.
     * Use this for projects that have been successfully parsed before. Otherwise it is advisable to use
     * the non-static {@link #load(File)} or {@link #loadParseOnly(File)} methods instead.
     * 
     * @param folder folder in which the PROJECT.xml file resides
     * @return a {@link Project} instance or {@code null} in case something went wrong
     */
    static public Project ParseProjectXMLInFolder(File folder) {
        return ParseProjectXMLInFolder(folder, null);
    }

    /**
     * Parses the {@value #PROJECT_FILE} file in the given folder to produce a {@link Project} instance.
     * If one is given the {@link FormSchemaInfoProvider} is used to speed up {@link Schema} generation.
     * No warnings are generated and no exceptions thrown.
     * Use this for projects that have been successfully parsed before. Otherwise it is advisable to use
     * the non-static {@link #load(File)} or {@link #loadParseOnly(File)} methods instead.
     * 
     * @param folder folder in which the {@value #PROJECT_FILE} file resides
     * @param fsiProvider a {@link FormSchemaInfoProvider}, or {@code null}
     * @return a {@link Project} instance or {@code null} in case something went wrong
     */
    static public Project ParseProjectXMLInFolder(File folder, FormSchemaInfoProvider fsiProvider) {
        return ParseProjectXML(GetProjectXMLFile(folder), fsiProvider);
    }

    /**
     * @param folder folder in which the {@value #PROJECT_FILE} file resides
     * @return the {@value #PROJECT_FILE} {@link File}
     */
    static public File GetProjectXMLFile(File folder) {
        return new File(folder, PROJECT_FILE);
    }

    /**
     * Parses the given {@value #PROJECT_FILE} file to produce a {@link Project} instance.
     * If one is given the {@link FormSchemaInfoProvider} is used to speed up {@link Schema} generation.
     * No warnings are generated and no exceptions thrown.
     * Use this for projects that have been successfully parsed before. Otherwise it is advisable to use
     * the non-static {@link #loadParseOnly(InputStream) method instead.
     * 
     * @param file the {@value #PROJECT_FILE} file
     * @param fsiProvider a {@link FormSchemaInfoProvider}, or {@code null}
     * @return a {@link Project} instance or {@code null} in case something went wrong
     */
    static public Project ParseProjectXML(File projectXML, FormSchemaInfoProvider fsiProvider) {
        try {
            return new ProjectParser().parseProject(projectXML, fsiProvider);
        } catch (Exception e) {
            System.err.println("Failed to load project from: " + projectXML.getAbsolutePath());
            e.printStackTrace(System.err);
            return null;
        }
    }

    /**
     * Parses the given {@link InputStream}, expected to provide the contents of a {@value #PROJECT_FILE} file,
     * to produce a {@link Project} instance. If one is given the {@link FormSchemaInfoProvider} is used
     * to speed up {@link Schema} generation.
     * No warnings are generated and no exceptions thrown.
     * Use this for projects that have been successfully parsed before. Otherwise it is advisable to use
     * the non-static {@link #load(File)} or {@link #loadParseOnly(File)} methods instead.  
     * 
     * @param projectXMLInputStream an {@link InputStream} providing the contents of a {@value #PROJECT_FILE} file
     * @param fsiProvider a {@link FormSchemaInfoProvider}, or {@code null}
     * @return a {@link Project} instance or {@code null} in case something went wrong
     */
    static public Project ParseProjectXML(InputStream projectXMLInputStream, FormSchemaInfoProvider fsiProvider) {
        try {
            return new ProjectParser().parseProject(projectXMLInputStream, fsiProvider);
        } catch (Exception e) {
            System.err.println("Failed to load project from InputStream");
            e.printStackTrace(System.err);
            return null;
        }
    }

    // DYNAMICS ----------------------------------------------------------
    /*package*/ final FileStorageProvider fileStorageProvider;
    private final ProjectChecker checker;
    private final PostProcessor postProcessor;
    private List<String> warnings;

    private final ProjectParser parser;

    /**
     * @param fileStorageProvider
     */
    public ProjectLoader(FileStorageProvider fileStorageProvider) {
        this(fileStorageProvider, null, null); // no post-processing, nor checking
    }

    /**
     * @param fileStorageProvider
     * @param postProcessor (may be null)
     * @param checker (may be null)
     */
    public ProjectLoader(FileStorageProvider fileStorageProvider, PostProcessor postProcessor,
            ProjectChecker checker) {
        if (fileStorageProvider == null)
            throw new NullPointerException("fileStorageProvider cannot be null!");
        this.fileStorageProvider = fileStorageProvider;
        this.postProcessor = postProcessor;
        this.checker = checker;
        this.parser = new ProjectParser();
    }

    /**
     * Extract the given sapelli file (provided as a File object) and parses the PROJECT.xml; returns the resulting Project object.
     * 
     * @param sapelliFile
     * @return the loaded Project
     * @throws Exception
     */
    public Project load(File sapelliFile) throws Exception {
        return load(FileHelpers.openInputStream(sapelliFile, true));
    }

    /**
     * Extract the given sapelli file (provided as an InputStream) and parses the PROJECT.xml; returns the resulting Project object.
     * 
     * @param sapelliFileInputStream
     * @return the loaded Project
     * @throws Exception
     */
    public Project load(InputStream sapelliFileInputStream) throws Exception {
        clearWarnings();
        Project project = null;
        File extractFolder = null;
        try {
            // STEP 0 - Create the extraction folder:
            extractFolder = new File(fileStorageProvider.getTempFolder(true), "" + System.currentTimeMillis());
            if (!FileHelpers.createDirectory(extractFolder))
                throw new FileStorageException("Could not create folder to extract project file into.");

            // STEP 1 - Extract the content of the Sapelli file to a new subfolder of the temp folder:
            try {
                if (Unzipper.unzip(sapelliFileInputStream, extractFolder) == 0)
                    throw new Exception("Sapelli file is not a valid ZIP archive or does not contain any files.");
            } catch (IOException ioe) {
                throw new Exception("Error on extracting contents of Sapelli file.", ioe.getCause());
            }

            // STEP 2 - Parse PROJECT.xml:
            try {
                project = parser
                        .parseProject(new File(extractFolder.getAbsolutePath() + File.separator + PROJECT_FILE));
            } catch (Exception e) {
                throw new Exception("Error on parsing " + PROJECT_FILE, e);
            }
            // Copy parser warnings:
            addWarnings(parser.getWarnings());

            // STEP 3 - Check if project is acceptable:
            checkProject(project); // throws IllegalArgumentException if something is wrong

            // STEP 4 - Create move extracted files to project folder:
            try {
                File installFolder = fileStorageProvider.getProjectInstallationFolder(project, true);
                FileHelpers.moveDirectory(extractFolder, installFolder);
                extractFolder = installFolder;
            } catch (Exception e) {
                throw new Exception("Error on moving extracted files to project folder.", e);
            }

            // STEP 5 - Run post-processing tasks:
            List<PostProcessTask> tasks = parser.getPostProcessingTasks();
            if (!tasks.isEmpty()) {
                if (postProcessor != null) {
                    postProcessor.initialise(project);
                    for (PostProcessTask task : tasks) {
                        try {
                            task.execute(postProcessor, project, this);
                        } catch (Exception e) {
                            throw new Exception("Error on executing post-processing task", e);
                        }
                    }

                    postProcessor.freeResources();
                } else
                    addWarning("Unable to perform " + tasks.size() + " post-processing");
            }
        } catch (Exception e) {
            // Delete temp or install folder:
            FileUtils.deleteQuietly(extractFolder);

            // Re-throw Exception:
            throw e;
        }

        // Return project object:
        return project;
    }

    /**
     * @param project
     * @throws IllegalArgumentException when the project is not acceptable
     */
    protected void checkProject(Project project) throws IllegalArgumentException {
        if (checker != null)
            checker.checkProject(project); // throws IllegalArgumentException if something is wrong
    }

    /**
     * Parses the PROJECT.xml present in the given sapelli file (provided as a File object), without extracting the contents to storage and without executing load tasks; returns the resulting Project object.
     * 
     * @param sapelliFile
     * @return the loaded Project
     * @throws Exception
     */

    public Project loadParseOnly(File sapelliFile) throws Exception {
        if (sapelliFile == null || !sapelliFile.exists() || sapelliFile.length() == 0)
            throw new IllegalArgumentException("Invalid Sapelli file");
        return loadParseOnly(new FileInputStream(sapelliFile));
    }

    /**
     * Parses the PROJECT.xml present in the given sapelli file (provided as an InputStream), without extracting the contents to storage and without executing load tasks; returns the resulting Project object.
     * 
     * @param sapelliFileStream
     * @return the loaded Project
     * @throws Exception
     */
    public Project loadParseOnly(InputStream sapelliFileStream) throws Exception {
        clearWarnings();
        try { // Parse PROJECT.xml:
            Project project = parser
                    .parseProject(Unzipper.getInputStreamForFileInZip(sapelliFileStream, PROJECT_FILE));
            // Copy parser warnings:
            addWarnings(parser.getWarnings());
            // Check if project is acceptable:
            checkProject(project); // throws IllegalArgumentException if something is wrong
            // all OK:
            return project;
        } catch (Exception e) {
            throw new Exception("Error on parsing " + PROJECT_FILE, e);
        }
    }

    @Override
    public void addWarning(String warning) {
        if (warnings == null)
            warnings = new ArrayList<String>();
        warnings.add(warning);
    }

    @Override
    public void addWarnings(Collection<String> warnings) {
        if (this.warnings == null)
            this.warnings = new ArrayList<String>();
        this.warnings.addAll(warnings);
    }

    @Override
    public List<String> getWarnings() {
        return warnings != null ? warnings : Collections.<String>emptyList();
    }

    @Override
    public void clearWarnings() {
        warnings = null;
    }

    /**
     * Callback interface for checking Project acceptance
     * 
     * @author mstevens
     */
    public interface ProjectChecker {

        /**
         * @param loadedProject
         * @throws IllegalArgumentException if something is wrong
         */
        public void checkProject(Project loadedProject) throws IllegalArgumentException;

    }

}