hadoopInstaller.installation.DeployInstallationFiles.java Source code

Java tutorial

Introduction

Here is the source code for hadoopInstaller.installation.DeployInstallationFiles.java

Source

package hadoopInstaller.installation;

/*
 * #%L
 * Hadoop Installer
 * %%
 * Copyright (C) 2015 - 2016 Julin Maestri
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */

import hadoopInstaller.exception.ExecutionError;
import hadoopInstaller.exception.InstallationError;
import hadoopInstaller.logging.MessageFormattingLog;

import java.text.MessageFormat;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Observable;
import java.util.Observer;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSelectInfo;
import org.apache.commons.vfs2.FileSelector;
import org.apache.commons.vfs2.FileSystemException;

import com.jcraft.jsch.Session;

public class DeployInstallationFiles {

    public class MD5ComparingFileSelector extends Observable implements FileSelector {
        private Host o_host;
        private Session o_session;
        private Map<String, String> filesMD5;
        private FileObject o_remoteDirectory;

        public MD5ComparingFileSelector(Host aHost, Session aSession, Map<String, String> aFilesMD5Map,
                FileObject aRemoteDirectory) {
            this.o_session = aSession;
            this.o_host = aHost;
            this.filesMD5 = aFilesMD5Map;
            this.o_remoteDirectory = aRemoteDirectory;
        }

        @Override
        public boolean includeFile(FileSelectInfo fileInfo) {
            // The base folder should be included.
            if (fileInfo.getBaseFolder().equals(fileInfo.getFile())) {
                return true;
            }
            /*
             * Set changed independent of the return type, else the observers
             * don't get notified.
             */
            setChanged();
            String fileName = fileInfo.getFile().getName().getBaseName();
            /*
             * Files in the configuration.xml <files> section should all be
             * loaded into the Map
             */
            if (!this.filesMD5.containsKey(fileName)) {
                notifyObservers(new Result(false, fileName, Reason.FILE_NOT_IN_UPLOAD_LIST));
                return false;
            }
            SshCommandExecutor md5Command = new SshCommandExecutor(this.o_session);
            try {
                if (!this.o_remoteDirectory.resolveFile(fileName).exists()) {
                    notifyObservers(new Result(true, fileName, Reason.FILE_NOT_PRESENT));
                    return true;
                }
            } catch (FileSystemException e) {
                notifyObservers(new Result(true, fileName, Reason.COULD_NOT_DETERMINE_FILE_EXISTANCE));
                return true;
            }
            try {
                md5Command.execute(MessageFormat.format("cd {0}; md5sum --binary {1} | grep -o ''^[0-9a-f]*''", //$NON-NLS-1$
                        this.o_host.getInstallationDirectory(), fileName));
            } catch (ExecutionError e) {
                notifyObservers(new Result(true, fileName, Reason.COULD_NOT_CALCULATE_MD5,
                        md5Command.getError().toString()));
                return true;
            }
            String md5 = md5Command.getOutput().get(0);
            boolean md5Matches = this.filesMD5.get(fileName).equals(md5);
            if (md5Matches) {
                notifyObservers(new Result(false, fileName, Reason.MD5_MATCHES));
            } else {
                notifyObservers(new Result(true, fileName, Reason.MD5_DOES_NOT_MATCH));
            }
            return !md5Matches;
        }

        @Override
        public boolean traverseDescendents(FileSelectInfo fileInfo) throws Exception {
            // Only copy the base folder contents, not it's sub-directories.
            return fileInfo.getBaseFolder().equals(fileInfo.getFile());
        }
    }

    public class MD5ComparingSelectorLogger implements Observer {

        private MessageFormattingLog log;

        public MD5ComparingSelectorLogger(MessageFormattingLog messageFormattingLog) {
            this.log = messageFormattingLog;
        }

        @Override
        public void update(Observable aSelector, Object anObject) {
            Result result = (Result) anObject;
            switch (result.getReason()) {
            case COULD_NOT_CALCULATE_MD5:
                this.log.debug("DeployInstallationFiles.MD5CouldNotBeCalculated", //$NON-NLS-1$
                        result.getFileName());
                if (result.hasDescription()) {
                    this.log.debug(result.getDescription().trim());
                }
                break;
            case FILE_NOT_IN_UPLOAD_LIST:
                this.log.debug("DeployInstallationFiles.FileNotInConfigurationFile", //$NON-NLS-1$
                        result.getFileName(), InstallerConstants.TGZ_BUNDLES_FOLDER,
                        InstallerConstants.CONFIGURATION_FILE);
                break;
            case MD5_DOES_NOT_MATCH:
                this.log.debug("DeployInstallationFiles.MD5DoesNotMatch", result.getFileName()); //$NON-NLS-1$
                break;
            case MD5_MATCHES:
                this.log.debug("DeployInstallationFiles.MD5Matches", //$NON-NLS-1$
                        result.getFileName());
                break;
            case FILE_NOT_PRESENT:
                this.log.debug("DeployInstallationFiles.FileNotPresent", result.getFileName()); //$NON-NLS-1$
                break;
            case COULD_NOT_DETERMINE_FILE_EXISTANCE:
                this.log.debug("DeployInstallationFiles.CouldNotDetermineFileExistance", //$NON-NLS-1$
                        result.getFileName());
                break;
            default:
                this.log.warn("DeployInstallationFiles.UnhandledSwitchCase", //$NON-NLS-1$
                        result.getFileName(), result.getReason().toString());
                break;
            }
            if (result.isIncluded()) {
                this.log.debug("DeployInstallationFiles.WillUpload", //$NON-NLS-1$
                        result.getFileName());
            } else {
                this.log.debug("DeployInstallationFiles.SkippingFile", //$NON-NLS-1$
                        result.getFileName());
            }
        }
    }

    public enum Reason {
        COULD_NOT_CALCULATE_MD5, FILE_NOT_IN_UPLOAD_LIST, MD5_DOES_NOT_MATCH, MD5_MATCHES, FILE_NOT_PRESENT, COULD_NOT_DETERMINE_FILE_EXISTANCE
    }

    public class Result {
        private String description;
        private String fileName;
        private boolean included;
        private Reason reason;

        public Result(boolean isIncluded, String aFileName, Reason aReason) {
            this.included = isIncluded;
            this.fileName = aFileName;
            this.reason = aReason;
            this.description = null;
        }

        public Result(boolean isIncluded, String aFileName, Reason aReason, String aDescription) {
            this.included = isIncluded;
            this.fileName = aFileName;
            this.reason = aReason;
            this.description = aDescription;
        }

        public String getDescription() {
            return this.description;
        }

        public String getFileName() {
            return this.fileName;
        }

        public Reason getReason() {
            return this.reason;
        }

        public boolean hasDescription() {
            return getDescription() != null;
        }

        public boolean isIncluded() {
            return this.included;
        }
    }

    private Installer installer;

    private FileObject remoteDirectory;

    private Session session;

    private Host host;

    private MessageFormattingLog log;

    public DeployInstallationFiles(Host aHost, Session aSession, FileObject aRemoteDirectory,
            Installer anInstaller) {
        this.host = aHost;
        this.session = aSession;
        this.remoteDirectory = aRemoteDirectory;
        this.installer = anInstaller;
        this.log = installer.getLog();
    }

    private void decompressFiles() throws InstallationError {
        for (Entry<String, String> fileEntry : this.installer.getConfig().getFiles().entrySet()) {
            String fileName = fileEntry.getValue();
            String linkName = fileEntry.getKey();
            String commandString = MessageFormat.format("cd {0}; tar -zxf {1}; ln -sf {2} {3}", //$NON-NLS-1$
                    this.host.getInstallationDirectory(), fileName, this.installer.getDirectories().get(linkName),
                    linkName);
            SshCommandExecutor command = new SshCommandExecutor(this.session);
            try {
                command.execute(commandString);
            } catch (ExecutionError e) {
                if (!command.getError().isEmpty()) {
                    log.error(command.getError());
                }
                throw new InstallationError(e, "DeployInstallationFiles.ErrorDecompressingFiles", //$NON-NLS-1$
                        this.host.getHostname());
            }
            if (!command.getOutput().isEmpty()) {
                for (String line : command.getOutput()) {
                    log.debug(line);
                }
            }
            if (this.installer.getConfig().deleteBundles())
                try {
                    this.remoteDirectory.resolveFile(fileName).delete();
                    log.info("DeployInstallationFiles.DeletingBundle", fileName, //$NON-NLS-1$
                            this.remoteDirectory.getName().getURI());
                } catch (FileSystemException e) {
                    throw new InstallationError(e, "DeployInstallationFiles.CouldNotDeleteBundle", //$NON-NLS-1$
                            fileName, this.remoteDirectory.getName().getURI());
                }
        }
    }

    public void run() throws InstallationError {
        FileObject dependenciesFolder;
        log.debug("DeployInstallationFiles.DeployingStarted", //$NON-NLS-1$
                this.host.getHostname());
        try {
            dependenciesFolder = this.installer.getLocalDirectory()
                    .resolveFile(InstallerConstants.TGZ_BUNDLES_FOLDER);
        } catch (FileSystemException e) {
            throw new InstallationError(e, "DeployInstallationFiles.CouldNotOpenFile", //$NON-NLS-1$
                    InstallerConstants.TGZ_BUNDLES_FOLDER);
        }
        if (this.installer.getConfig().deleteOldFiles()) {
            try {
                /*
                 * Current version of commons-vfs can not delete symlinks to a
                 * folder because it thinks of them as a folder and then fails
                 * to delete them.
                 * 
                 * Workaround, connect and do rm -rf
                 */
                // this.remoteDirectory.delete(new AllFileSelector());
                SshCommandExecutor command = new SshCommandExecutor(this.session);
                command.execute(MessageFormat.format("rm -rf {0}/*", //$NON-NLS-1$
                        this.remoteDirectory.getName().getPath()));
            } catch (ExecutionError e) {
                throw new InstallationError(e, "DeployInstallationFiles.CouldNotDeleteOldFiles", //$NON-NLS-1$
                        this.remoteDirectory.getName().getURI());
            }
            log.info("DeployInstallationFiles.DeletingOldFiles", //$NON-NLS-1$
                    this.remoteDirectory.getName().getURI());
        }
        uploadFiles(dependenciesFolder);
        decompressFiles();
        try {
            dependenciesFolder.close();
        } catch (FileSystemException e) {
            log.warn("DeployInstallationFiles.CouldNotCloseFile", //$NON-NLS-1$
                    dependenciesFolder.getName().getURI());
        }
        log.debug("DeployInstallationFiles.DeployingFinished", //$NON-NLS-1$
                this.host.getHostname());
    }

    private void uploadFiles(FileObject dependenciesFolder) throws InstallationError {
        /*
         * Copy the files if they do not exist or if the existing file hash does
         * not match with the one calculated previously.
         * 
         * The log is done with an observer on the file selector.
         */
        MD5ComparingFileSelector selector = new MD5ComparingFileSelector(this.host, this.session,
                this.installer.getFileHashes(), this.remoteDirectory);
        MD5ComparingSelectorLogger observer = new MD5ComparingSelectorLogger(log);
        log.debug("DeployInstallationFiles.UploadingFiles", //$NON-NLS-1$
                this.host.getHostname());
        selector.addObserver(observer);
        try {
            this.remoteDirectory.copyFrom(dependenciesFolder, selector);
        } catch (FileSystemException e) {
            throw new InstallationError(e, "DeployInstallationFiles.ErrorUploadingFiles", //$NON-NLS-1$
                    this.host.getHostname());
        }
        selector.deleteObserver(observer);
    }
}