eu.optimis.vc.api.IsoCreator.IsoImageCreation.java Source code

Java tutorial

Introduction

Here is the source code for eu.optimis.vc.api.IsoCreator.IsoImageCreation.java

Source

/**
 *  Copyright 2013 University of Leeds
 *
 *  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 eu.optimis.vc.api.IsoCreator;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;

import eu.optimis.manifest.api.sp.Manifest;
import eu.optimis.vc.api.Core.SystemCall;
import eu.optimis.vc.api.Core.SystemCallException;
import eu.optimis.vc.api.DataModel.ContextData;
import eu.optimis.vc.api.DataModel.GlobalConfiguration;
import eu.optimis.vc.api.DataModel.VirtualMachine;
import eu.optimis.vc.api.DataModel.ContextDataTypes.EndPoint;
import eu.optimis.vc.api.DataModel.ContextDataTypes.LicenseToken;
import eu.optimis.vc.api.DataModel.ContextDataTypes.SecurityKey;
import eu.optimis.vc.api.DataModel.ContextDataTypes.SoftwareDependency;
import eu.optimis.vc.api.DataModel.Image.Iso;

/**
 * Class to create ISO images for containing contextualization data.
 * 
 * @author Django Armstrong (ULeeds)
 * @version 0.0.4
 */
public class IsoImageCreation {

    private static final String IO_EXCEPTION = "IOException: ";

    private static final String FILE_NOT_FOUND_EXCEPTION = "FileNotFoundException: ";

    private static final String CREATED_FILE = "Created file: ";

    private static final String ATTEMPTING_TO_CREATE_FILE = "Attempting to create file: ";

    protected static final Logger LOGGER = Logger.getLogger(IsoImageCreation.class);

    private Iso iso;
    private GlobalConfiguration configuration;
    private SystemCall systemCall;
    private String isoDataDirectory;
    private Boolean addRecontextFiles;

    private Manifest manifest;

    private ContextData contextData;
    private VirtualMachine virtualMachine;

    /**
     * Initialises an instance of the ISO Image creator.
     * 
     * @param iso
     *            The ISO to create.
     * @param configuration
     *            Configuration details used when creating the ISO.
     */
    public IsoImageCreation(Iso iso, GlobalConfiguration configuration, Manifest manifest) {
        this.iso = iso;
        this.configuration = configuration;
        this.manifest = manifest;
        this.systemCall = new SystemCall(configuration.getInstallDirectory());
        this.isoDataDirectory = configuration.getContextDataDirectory() + File.separator + iso.getFileName();
        this.addRecontextFiles = configuration.getAddRecontextFiles();
        LOGGER.debug("Read addRecontextFiles from config: " + this.addRecontextFiles);
    }

    /**
     * Create new IsoImageCreation object for recontextualization
     * @param configuration The global configuration
     * @param existingIsoPath The path to the existing ISO file
     * @throws IOException If the existing ISO cannot be found, or if a temp directory cannot be created.
     */
    public IsoImageCreation(GlobalConfiguration configuration, String existingIsoPath) throws IOException {
        String fileName = "recontext_" + existingIsoPath.substring(existingIsoPath.lastIndexOf(File.separator) + 1,
                existingIsoPath.length());
        // Not used in this context, for VirtualMachine 
        String imageId = "0";
        String uri = configuration.getRepository() + File.separator + fileName;
        String format = "ISO9660";
        // TODO: Not currently used? Investigate.
        String mountPoint = "/media/context/";
        this.iso = new Iso(imageId, fileName, uri, format, mountPoint);
        this.configuration = configuration;
        this.systemCall = new SystemCall(configuration.getInstallDirectory());
        this.isoDataDirectory = configuration.getContextDataDirectory() + File.separator + iso.getFileName();
        this.addRecontextFiles = configuration.getAddRecontextFiles();
    }

    // TODO Add javadoc here
    // 2) Write out the end points to each to there own file in a
    // sub-directory:   
    private void storeEndpoints() {

        File endPointDirectory = new File(isoDataDirectory + File.separator + "endpoints");
        endPointDirectory.mkdirs();

        if (virtualMachine.getEndPoints().size() != 0) {
            for (EndPoint endPoint : virtualMachine.getEndPoints().values()) {
                String name = endPoint.getName();
                String uri = endPoint.getUri();

                // Create the end point with the given name
                File endPointFile = new File(endPointDirectory + File.separator + name + ".properties");
                try {
                    LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + endPointFile.getPath());
                    endPointFile.createNewFile();
                    LOGGER.debug(CREATED_FILE + endPointFile.getPath());
                } catch (IOException e) {
                    LOGGER.error("Failed to create endpoint file with name: " + name + ".properties", e);
                }

                Properties props = new Properties();
                props.setProperty(name, uri);
                try {
                    FileOutputStream fileOutputStream = new FileOutputStream(endPointFile);
                    props.store(fileOutputStream, "VMC properties file for service endpoints:");
                    fileOutputStream.close();
                    LOGGER.debug("Writing endpoint complete!");

                } catch (FileNotFoundException e) {
                    LOGGER.error(FILE_NOT_FOUND_EXCEPTION + e);
                } catch (IOException e) {
                    LOGGER.error(IO_EXCEPTION + e);
                }

            }
        } else {
            LOGGER.warn("No end points to write!");
        }
    }

    // TODO Add javadoc here
    // 1) Store the security keys if the are to be added to this VM
    // instance
    private void storeSecurityKeys() {
        File securityKeysDirectory = new File(isoDataDirectory + File.separator + "securitykeys");
        securityKeysDirectory.mkdirs();

        if (virtualMachine.isHasVPNKey() || virtualMachine.isHasSSHKey() || virtualMachine.isHasBTKey()
                || virtualMachine.isHasDMKey()) {
            if (contextData.getSecurityKeys().size() != 0) {
                for (SecurityKey securityKey : contextData.getSecurityKeys().values()) {
                    String name = securityKey.getName();
                    byte[] keyData = securityKey.getKeyData();

                    File securityKeyFile = null;
                    // TODO: change to switch... meh
                    if (name.equals("SSH")) {
                        securityKeyFile = new File(securityKeysDirectory + File.separator + "SSH.key");
                    } else if (name.equals("VPN")) {
                        securityKeyFile = new File(securityKeysDirectory + File.separator + "VPN.key");
                    } else if (name.equals("BT")) {
                        securityKeyFile = new File(securityKeysDirectory + File.separator + "BT.key");
                    } else if (name.equals("DM")) {
                        securityKeyFile = new File(securityKeysDirectory + File.separator + "DM.key");
                    } else {
                        LOGGER.warn("Unknown key name type!");
                    }

                    try {
                        LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + securityKeyFile.getPath());
                        securityKeyFile.createNewFile();
                        LOGGER.debug(CREATED_FILE + securityKeyFile.getPath());
                    } catch (IOException e) {
                        LOGGER.error("Failed to create security key file with name: " + name + ".key", e);
                    }

                    // Write out security key...
                    LOGGER.warn("Writing out security key with path: " + securityKeyFile.getPath());
                    try {
                        FileOutputStream fos = new FileOutputStream(securityKeyFile);
                        fos.write(keyData);
                        fos.close();
                        LOGGER.debug("Writing security key complete!");

                    } catch (FileNotFoundException e) {
                        LOGGER.error(FILE_NOT_FOUND_EXCEPTION + e);
                    } catch (IOException e) {
                        LOGGER.error(IO_EXCEPTION + e);
                    }
                }
            } else {
                LOGGER.warn("No security keys to write!");
            }
        }
    }

    // TODO Add javadoc here
    // 3) Provide per VM instance configuration information for software
    // dependencies
    private void storeSoftwareDependencies() {
        File softwareDependenciesDirectory = new File(isoDataDirectory + File.separator + "softwaredeps");
        softwareDependenciesDirectory.mkdirs();

        if (virtualMachine.getSoftwareDependencies().size() != 0) {
            for (SoftwareDependency softwareDependency : virtualMachine.getSoftwareDependencies().values()) {
                String name = softwareDependency.getArtifactId() + "_" + softwareDependency.getGroupId() + "_"
                        + softwareDependency.getVersion() + ".dep";

                // Create the software dependency files here...
                File softwareDependencyFile = new File(softwareDependenciesDirectory + File.separator + name);
                try {
                    LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + softwareDependencyFile.getPath());
                    softwareDependencyFile.createNewFile();
                    LOGGER.debug(CREATED_FILE + softwareDependencyFile.getPath());
                } catch (IOException e) {
                    LOGGER.error("Failed to create softwareDependency config file with name: " + name, e);
                }

                // TODO Write out some configuration data to the new file.
                LOGGER.warn(
                        "Writing out software dependency configuration data not implemented yet, no support in service manifest!");
            }
        } else {
            LOGGER.warn("No software dependency configuration files to write!");
        }
    }

    // TODO Add javadoc here   
    // 4) Store the license tokens
    private void storeLicenseKeys() {

        File licenseTokenDirectory = new File(isoDataDirectory + File.separator + "licensetoken");
        licenseTokenDirectory.mkdirs();

        if (virtualMachine.getLicenseTokens().size() != 0) {
            for (LicenseToken licenseToken : virtualMachine.getLicenseTokens().values()) {
                String name = "license.token." + licenseToken.getId();

                // Create the end point with the given name and write the
                File licenseTokenFile = new File(licenseTokenDirectory + File.separator + name);
                try {
                    LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + licenseTokenFile.getPath());
                    licenseTokenFile.createNewFile();
                    LOGGER.debug(CREATED_FILE + licenseTokenFile.getPath());
                } catch (IOException e) {
                    LOGGER.error("Failed to create licenseToken file with name: " + name, e);
                }

                // Write out license tokens...
                LOGGER.warn("Writing out license token...");
                try {
                    FileOutputStream fos = new FileOutputStream(licenseTokenFile);
                    fos.write(licenseToken.getToken());
                    fos.close();
                    LOGGER.warn("Writing license token complete!");
                } catch (FileNotFoundException e) {
                    LOGGER.error(FILE_NOT_FOUND_EXCEPTION + e);
                } catch (IOException e) {
                    LOGGER.error(IO_EXCEPTION + e);
                }
            }
        }
    }

    // TODO Add javadoc here
    // 5) Store the entire manifest
    private void storeManifest() {
        File serviceManifestFile = new File(isoDataDirectory + File.separator + "manifest.xml");
        try {
            LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + serviceManifestFile.getPath());
            serviceManifestFile.createNewFile();
            LOGGER.debug(CREATED_FILE + serviceManifestFile.getPath());

            // Write out the manifest file
            FileOutputStream fos = new FileOutputStream(serviceManifestFile.getPath());
            fos.write(manifest.toString().getBytes());
            fos.close();
            LOGGER.debug("Writing service manifest complete!");

        } catch (IOException e) {
            LOGGER.error("Failed to create service manfiest file with name: " + serviceManifestFile.getName(), e);
        }
    }

    // TODO Add javadoc here
    // 6) Bootstrap script
    private void storeBootstrapScripts(File scriptsDirectory) {
        File bootStrapFile = new File(scriptsDirectory + File.separator + "bootstrap.sh");
        try {
            LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + bootStrapFile.getPath());
            bootStrapFile.createNewFile();
            LOGGER.debug(CREATED_FILE + bootStrapFile.getPath());

            // TODO: This should be stored somewhere else and not hardcoded
            // Mount location is currently hard coded in the init.d scripts of
            // the base VM /mnt/context/
            String bootStrapScript = "#!/bin/bash\n" + "if [ -f /mnt/context/scripts/bootstrap.sh ]; then\n"
                    + "  #Get the public SSH key:\n"
                    + "  PUBLICKEY=`cat /mnt/context/securitykeys/SSH.key | grep ssh-rsa`\n"
                    + "  echo ${PUBLICKEY/user@hostname/root@`hostname`} > /root/.ssh/authorized_keys\n"
                    + "  chmod 700 /root/.ssh\n" + "  chmod 600 /root/.ssh/authorized_keys\n"
                    + "  #Get the private SSH key:\n"
                    + "  cat /mnt/context/securitykeys/SSH.key | head -27 > /root/.ssh/id_rsa\n"
                    + "  chmod 700 /root/.ssh/id_rsa\n" + "  #Run agent script if present\n"
                    + "  if [ -f /mnt/context/scripts/agents.sh ]; then\n"
                    + "    sh /mnt/context/scripts/agents.sh\n" + "  fi\n" + "fi\n";

            // Write out the boostrap file
            FileOutputStream fos = new FileOutputStream(bootStrapFile.getPath());
            fos.write(bootStrapScript.getBytes());
            fos.close();
            LOGGER.debug("Writing boobstrap script complete!");

        } catch (IOException e) {
            LOGGER.error("Failed to create boobstrap script file with name: " + bootStrapFile.getName(), e);
        }
    }

    // TODO Add javadoc here
    // 7) Security Agents
    private void storeAgents(File scriptsDirectory) {
        if (virtualMachine.isHasIPS() || virtualMachine.isHasBTKey() || virtualMachine.isHasVPNKey()) {
            LOGGER.debug("Adding agents to ISO");

            // Add the agent tar ball
            String agentsTarBallName = "vpn.tar.gz";
            // Agents tar ball source
            File agentsTarBallFileSource = new File(
                    configuration.getAgentsDirectory() + File.separator + agentsTarBallName);
            LOGGER.debug("agentsTarBallFileSource is: " + agentsTarBallFileSource.getPath());
            // Destination folder
            File agentsIsoDirectory = new File(isoDataDirectory + File.separator + "agents");
            agentsIsoDirectory.mkdirs();
            LOGGER.debug("agentsIsoDirectory is: " + agentsIsoDirectory.getPath());
            // Agents tar ball destination
            File agentsTarBallFileDestination = new File(agentsIsoDirectory + File.separator + agentsTarBallName);
            LOGGER.debug("agentsTarBallFileDestination is: " + agentsTarBallFileDestination.getPath());

            // Copy the file to the iso directory
            LOGGER.debug("Copying agent file to ISO directory...");
            FileChannel source = null;
            FileChannel destination = null;
            try {
                if (!agentsTarBallFileDestination.exists()) {
                    agentsTarBallFileDestination.createNewFile();
                }
                source = new FileInputStream(agentsTarBallFileSource).getChannel();
                destination = new FileOutputStream(agentsTarBallFileDestination).getChannel();
                destination.transferFrom(source, 0, source.size());
                LOGGER.debug(
                        "Copied agent file to ISO directory, size is : " + agentsTarBallFileDestination.length());
                agentsTarBallFileDestination.getTotalSpace();
            } catch (IOException e) {
                LOGGER.error(
                        "Failed to create agents tar ball with path: " + agentsTarBallFileDestination.getPath(), e);
            } finally {
                if (source != null) {
                    try {
                        source.close();
                    } catch (IOException e) {
                        LOGGER.error("Failed to close source agents tar ball file with path: "
                                + agentsTarBallFileSource.getPath(), e);
                    }
                }
                if (destination != null) {
                    try {
                        destination.close();
                    } catch (IOException e) {
                        LOGGER.error("Failed to close destination agents tar ball file with path: "
                                + agentsTarBallFileDestination.getPath(), e);
                    }
                }
            }

            // Add the agent script
            File agentsFile = new File(scriptsDirectory + File.separator + "agents.sh");
            try {
                LOGGER.debug(ATTEMPTING_TO_CREATE_FILE + agentsFile.getPath());
                agentsFile.createNewFile();
                LOGGER.debug(CREATED_FILE + agentsFile.getPath());

                // TODO: This should be stored somewhere else and not hardcoded
                // Mount location is currently hard coded in the init.d scripts
                // of the base VM /mnt/context/
                String agentsScript = "#!/bin/bash\n" + "#Setup environment\n" + "touch /var/lock/subsys/local\n"
                        + "source /etc/profile\n" + "\n"
                        + "#Extract the agent from the ISO agent directory to /opt/optimis/vpn/\n"
                        + "mkdir -p /opt/optimis\n" + "tar zxvf /mnt/context/agents/" + agentsTarBallName
                        + " -C /opt/optimis/\n" + "chmod -R 777 /opt/optimis/vpn\n" + "\n"
                        + "#Install and start the agents\n" + "\n";

                // Add VPN install and init script to
                // /mnt/context/scripts/agents.sh
                if (virtualMachine.isHasIPS()) {
                    agentsScript += "#IPS\n" + "/opt/optimis/vpn/IPS_Meta.sh\n"
                            + "/bin/date > /opt/optimis/vpn/dsa.log\n" + "\n";
                }

                // Add VPN install and init script to
                // /mnt/context/scripts/agents.sh ?

                // KMS
                if (virtualMachine.isHasBTKey()) {
                    agentsScript += "#KMS\n" + "/opt/optimis/vpn/KMS_Meta.sh\n"
                            + "/bin/date >> /opt/optimis/vpn/scagent.log\n" + "\n";
                }

                // Add VPN install and init script to
                // /mnt/context/scripts/agents.sh
                if (virtualMachine.isHasVPNKey()) {
                    agentsScript += "#VPN\n" + "/opt/optimis/vpn/VPN_Meta.sh\n";
                }

                // Write out the agents file
                FileOutputStream fos = new FileOutputStream(agentsFile.getPath());
                fos.write(agentsScript.getBytes());
                fos.close();
                LOGGER.debug("Writing agents script complete!");

            } catch (IOException e) {
                LOGGER.error("Failed to create agents script file with path: " + agentsFile.getPath(), e);
            }
        } else {
            LOGGER.debug("Agents not not needed by service!");
        }

    }

    // TODO Add javadoc here
    // 8) Add recontextualization files
    private void refactorFiles(boolean addRecontextScripts) {
        LOGGER.debug("addRecontextFiles : " + addRecontextFiles);
        //FIXME changes this to the an ISO structure version number
        if (addRecontextFiles) {
            LOGGER.debug("Performing ISO recontextualization changes...");

            LOGGER.debug("Restructuring ISO");
            // Create the data directory
            String recontextIsoDataDirectory = isoDataDirectory + File.separator + "data";
            // Move the contents in the root of the iso to the new data
            // directory
            LOGGER.debug("Copying context data from: '" + isoDataDirectory + "' to: '" + recontextIsoDataDirectory
                    + "'");

            File recontextIsoDataDirectoryFile = new File(recontextIsoDataDirectory);
            recontextIsoDataDirectoryFile.mkdir();

            File[] fileList = new File(isoDataDirectory).listFiles();
            for (File file : fileList) {
                try {
                    if (file.isDirectory()) {
                        LOGGER.debug("Considering directory: " + file.getName());
                        if (!file.getName().equalsIgnoreCase("data")) {
                            File newDir = new File(recontextIsoDataDirectoryFile + File.separator + file.getName());
                            FileUtils.copyDirectory(file, newDir);
                            LOGGER.debug("Copied directory: " + file.getAbsolutePath() + " to "
                                    + newDir.getAbsolutePath());
                        }
                    } else {
                        FileUtils.copyFile(file,
                                new File(recontextIsoDataDirectoryFile + File.separator + file.getName()));
                        LOGGER.debug("Copying file: " + file.getName());
                    }

                } catch (IOException e) {
                    LOGGER.error("Failed to copy old context data to recontext data directory", e);
                }
            }

            //Delete old context data
            LOGGER.debug("Deleting old context data");
            fileList = new File(isoDataDirectory).listFiles();
            for (File file : fileList) {
                if (!file.getName().equalsIgnoreCase("data")) {
                    try {
                        FileUtils.forceDelete(file);
                        LOGGER.debug("Deleted file or directory: '" + file.getPath() + "'");
                    } catch (IOException e) {
                        LOGGER.error("Failed to delete file or directory: '" + file.getPath() + "'", e);
                    }
                } else {
                    LOGGER.debug("Skipping deletion of 'data' directory");
                }
            }

            // Add the recontext-scripts directory to the iso
            if (addRecontextScripts) {
                String recontextIsoScriptDirectory = isoDataDirectory + File.separator + "recontext-scripts";
                String recontextScripts = configuration.getInstallDirectory() + File.separator + "templates"
                        + File.separator + "scripts" + File.separator + "recontext";
                LOGGER.debug("Copying recontext files from: '" + recontextScripts + "' to: '"
                        + recontextIsoScriptDirectory + "'");
                try {
                    FileUtils.copyDirectory(new File(recontextScripts), new File(recontextIsoScriptDirectory));
                } catch (IOException e) {
                    LOGGER.error("Failed to copy old context data to recontext data directory", e);
                }
            }

            LOGGER.debug("ISO recontextualization changes completed");

        } else {
            LOGGER.debug("Recontext files have not been specified in the VMC config, not altering ISO structure!");
        }
    }

    // TODO Add javadoc here
    // 9 ) Add ISO version info to props file 
    private void storeMetaDataFile(String version, String type) {
        LOGGER.debug("Creating meta.data properties file for folder stucture version=" + version);
        Properties metaDataVersion = new Properties();
        metaDataVersion.setProperty("version", version);
        metaDataVersion.setProperty("type", type);
        File metaDataFile = new File(isoDataDirectory + File.separator + ".metadata");

        LOGGER.debug("Adding meta.data to ISO");
        try {
            FileOutputStream fileOutputStream = new FileOutputStream(metaDataFile);
            metaDataVersion.store(fileOutputStream, "VMC ISO data structure version:");
            fileOutputStream.close();
            LOGGER.debug("Writing endpoint complete!");
        } catch (FileNotFoundException e) {
            LOGGER.error(FILE_NOT_FOUND_EXCEPTION + e);
        } catch (IOException e) {
            LOGGER.error(IO_EXCEPTION + e);
        }
    }

    /**
     * Store context data to the local repository. The following context data is
     * stored: 1) Security Keys 2) End Points 3) Software Dependencies 4)
     * License Tokens 5) Entire SP manifest 6) Bootstrap script 7) Security
     * Agents
     * 
     * TODO: Refactor reducing duplication of code used for directory and file
     * operations...
     * 
     * @param contextdata
     *            Used to access the security keys applied at the service level.
     * @param virtualMachine
     *            The virtual machine to store context data for.
     */
    public void storeContextData(ContextData contextData, VirtualMachine virtualMachine) {

        this.contextData = contextData;
        this.virtualMachine = virtualMachine;

        LOGGER.debug("Iso Data Directory is: " + isoDataDirectory + File.separator);

        // Create the directory
        new File(isoDataDirectory).mkdirs();

        // TODO: Add comments to phases of storage
        storeSecurityKeys();

        storeEndpoints();

        storeSoftwareDependencies();

        storeLicenseKeys();

        storeManifest();

        //Create scripts directory for bootstrap and agents
        File scriptsDirectory = new File(isoDataDirectory + File.separator + "scripts");
        scriptsDirectory.mkdirs();

        storeBootstrapScripts(scriptsDirectory);

        storeAgents(scriptsDirectory);

        //Refactor the ISO for recontext support
        refactorFiles(true);

        storeMetaDataFile("2", "context");
    }

    // TODO Add javadoc here
    // Creates a new recontextualization image (with test data), eventually will
    // take as input recontextualization data specific to an IP
    public void storeRecontextData(ContextData contextdata, VirtualMachine virtualMachine) {

        //TODO: contextData used for security keys but not implemented in this function yet 
        this.contextData = contextdata;
        this.virtualMachine = virtualMachine;

        LOGGER.debug("Iso Data Directory is: " + isoDataDirectory + File.separator);

        // Create the directory
        new File(isoDataDirectory).mkdirs();

        // Add endpoints to be stored in the recontext ISO
        storeEndpoints();

        //Refactor the ISO for recontext support without adding the recontext scripts
        refactorFiles(false);

        storeMetaDataFile("2", "recontext");
    }

    /**
     * Create an ISO using its associated attributes and stored context data.
     * 
     * @return The ISO created.
     * @throws SystemCallException
     *             Thrown if the command to create the ISO via a system call
     *             fails.
     */
    public Iso create() throws SystemCallException {

        // Detect Linux distribution
        String commandName;
        if (new File("/etc/debian_version").exists()) {
            LOGGER.info("Debian distribution Variant detected using \"genisoimage\"");
            commandName = "genisoimage";
        } else if (new File("/etc/redhat-release").exists()) {
            LOGGER.info("Redhat distribution variant detected using \"mkisofs\"");
            commandName = "mkisofs";
        } else {
            LOGGER.info("Unknown linux distribution detected using default \"mkisofs\"");
            commandName = "mkisofs";
        }

        ArrayList<String> arguments = new ArrayList<String>();

        // Generate SUSP and RR records
        arguments.add("-R");
        // File ownership and modes
        arguments.add("-r");
        // Generate Joliet directory records
        arguments.add("-J");
        // Allow full 31 character filenames
        arguments.add("-l");
        arguments.add("-allow-leading-dots");
        arguments.add("-allow-lowercase");
        arguments.add("-allow-multidot");
        // filename
        arguments.add("-o");
        arguments.add(iso.getUri());
        arguments.add(isoDataDirectory);

        // Executed command looks like so:
        // "mkisofs -o iso.getFileName() isoDataDirectory"
        try {
            systemCall.runCommand(commandName, arguments);
        } catch (SystemCallException e) {
            if (configuration.isDefaultValues()) {
                LOGGER.warn("Failed to run command, is this invocation in a unit test?", e);
            } else {
                throw e;
            }
        }

        if (systemCall.getReturnValue() == 0) {
            iso.setCreated(true);
            LOGGER.info("Iso created with uri: " + iso.getUri());
        } else {
            LOGGER.error("Iso Creation Failed! Return value was: " + systemCall.getReturnValue());
        }

        // Print out the directory tree structure for debug purposes:
        LOGGER.debug("Files in directory: " + isoDataDirectory + File.separator);
        try {
            List<File> files = getFileListing(new File(isoDataDirectory));
            for (File file : files) {
                LOGGER.debug(file);
            }
        } catch (Exception e) {
            LOGGER.error("File was not found while listing directory!", e);
        }

        // Remove isoDataDirectory recursively after creating the ISO:
        try {
            deleteRecursive(new File(isoDataDirectory));
            LOGGER.debug("Recursively deleted isoDataDirectory: " + isoDataDirectory + File.separator);
        } catch (FileNotFoundException e) {
            LOGGER.error("Cannot recursively delete isoDataDirectory: " + isoDataDirectory, e);
        }

        return iso;
    }

    /**
     * Delete a directory recursively, this does the equivalent of "rm -r".
     * 
     * TODO: move this to a utils class..
     * 
     * @param path
     *            Root File Path.
     * @return True if the file and all sub files/directories have been removed.
     * @throws FileNotFoundException.
     */
    private static boolean deleteRecursive(File path) throws FileNotFoundException {
        if (!path.exists()) {
            throw new FileNotFoundException(path.getAbsolutePath());
        }

        boolean ret = true;
        if (path.isDirectory()) {
            for (File f : path.listFiles()) {
                ret = ret && deleteRecursive(f);
            }
        }
        return ret && path.delete();
    }

    /**
     * Recursively walk a directory tree and return a List of all Files found;
     * the List is sorted using File.compareTo().
     * 
     * TODO: move this to a utils class..
     * 
     * @param aStartingDir
     *            is a valid directory, which can be read.
     */
    private static List<File> getFileListing(File aStartingDir) throws FileNotFoundException {
        validateDirectory(aStartingDir);
        List<File> result = getFileListingNoSort(aStartingDir);
        Collections.sort(result);
        return result;
    }

    /**
     * Get file list without sorting.
     * 
     * TODO: Move this to a utils class...
     * 
     * @param aStartingDir
     *            The starting directory.
     * @return List of files found.
     * @throws FileNotFoundException
     */
    private static List<File> getFileListingNoSort(File aStartingDir) throws FileNotFoundException {
        List<File> result = new ArrayList<File>();
        File[] filesAndDirs = aStartingDir.listFiles();
        List<File> filesDirs = Arrays.asList(filesAndDirs);
        for (File file : filesDirs) {
            // always add, even if directory
            result.add(file);
            if (!file.isFile()) {
                // must be a directory
                // recursive call!
                List<File> deeperList = getFileListingNoSort(file);
                result.addAll(deeperList);
            }
        }
        return result;
    }

    /**
     * Directory is valid if it exists, does not represent a file, and can be
     * read.
     * 
     * TODO: Move this to a utils class...
     */
    private static void validateDirectory(File aDirectory) throws FileNotFoundException {
        if (aDirectory == null) {
            throw new IllegalArgumentException("Directory should not be null.");
        }
        if (!aDirectory.exists()) {
            throw new FileNotFoundException("Directory does not exist: " + aDirectory);
        }
        if (!aDirectory.isDirectory()) {
            throw new IllegalArgumentException("Is not a directory: " + aDirectory);
        }
        if (!aDirectory.canRead()) {
            throw new IllegalArgumentException("Directory cannot be read: " + aDirectory);
        }
    }

}