All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.xebialabs.overthere.spi.BaseOverthereConnection Maven / Gradle / Ivy

There is a newer version: 5.6.16
Show newest version
/*
 * Copyright (c) 2008-2014, XebiaLabs B.V., All rights reserved.
 *
 *
 * Overthere is licensed under the terms of the GPLv2
 * , like most XebiaLabs Libraries.
 * There are special exceptions to the terms and conditions of the GPLv2 as it is applied to
 * this software, see the FLOSS License Exception
 * .
 *
 * This program is free software; you can redistribute it and/or modify it under the terms
 * of the GNU General Public License as published by the Free Software Foundation; version 2
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth
 * Floor, Boston, MA 02110-1301  USA
 */
package com.xebialabs.overthere.spi;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.xebialabs.overthere.CmdLine;
import com.xebialabs.overthere.ConnectionOptions;
import com.xebialabs.overthere.OperatingSystemFamily;
import com.xebialabs.overthere.OverthereConnection;
import com.xebialabs.overthere.OverthereExecutionOutputHandler;
import com.xebialabs.overthere.OverthereFile;
import com.xebialabs.overthere.OverthereProcess;
import com.xebialabs.overthere.OverthereProcessOutputHandler;
import com.xebialabs.overthere.RuntimeIOException;
import com.xebialabs.overthere.local.LocalConnection;

import static com.xebialabs.overthere.util.OverthereUtils.checkNotNull;
import static com.xebialabs.overthere.ConnectionOptions.*;
import static com.xebialabs.overthere.util.ConsoleOverthereExecutionOutputHandler.syserrHandler;
import static com.xebialabs.overthere.util.ConsoleOverthereExecutionOutputHandler.sysoutHandler;
import static com.xebialabs.overthere.util.OverthereProcessOutputHandlerWrapper.wrapStderr;
import static com.xebialabs.overthere.util.OverthereProcessOutputHandlerWrapper.wrapStdout;
import static com.xebialabs.overthere.util.OverthereUtils.closeQuietly;
import static java.lang.String.format;

/**
 * A connection on a host (local or remote) on which to manipulate files and execute commands.
 * 

* All methods in this interface may throw a {@link com.xebialabs.overthere.RuntimeIOException} if an error occurs. * Checked {@link java.io.IOException IOExceptions} are never thrown. */ public abstract class BaseOverthereConnection implements OverthereConnection { private static Logger logger = LoggerFactory.getLogger(BaseOverthereConnection.class); protected final String protocol; protected final ConnectionOptions options; protected final AddressPortMapper mapper; protected final OperatingSystemFamily os; protected final int connectionTimeoutMillis; protected final int socketTimeoutMillis; protected final boolean canStartProcess; protected final String temporaryDirectoryPath; protected final boolean deleteTemporaryDirectoryOnDisconnect; protected final int temporaryFileCreationRetries; protected final String temporaryFileHolderDirectoryNamePrefix; protected final List temporaryFileHolderDirectories = new ArrayList(); protected final int streamBufferSize; protected int temporaryFileHolderDirectoryNameSuffix = 0; protected OverthereFile workingDirectory; private volatile boolean isConnected; private Throwable openStack; protected BaseOverthereConnection(final String protocol, final ConnectionOptions options, final AddressPortMapper mapper, final boolean canStartProcess) { this.protocol = checkNotNull(protocol, "Cannot create OverthereConnection with null protocol"); this.options = checkNotNull(options, "Cannot create OverthereConnection with null options"); this.mapper = checkNotNull(mapper, "Cannot create OverthereConnection with null address-port mapper"); this.os = options.getEnum(OPERATING_SYSTEM, OperatingSystemFamily.class); this.connectionTimeoutMillis = options.getInteger(CONNECTION_TIMEOUT_MILLIS, CONNECTION_TIMEOUT_MILLIS_DEFAULT); this.socketTimeoutMillis = options.getInteger(SOCKET_TIMEOUT_MILLIS, SOCKET_TIMEOUT_MILLIS_DEFAULT); this.canStartProcess = canStartProcess; this.temporaryDirectoryPath = options.get(TEMPORARY_DIRECTORY_PATH, os.getDefaultTemporaryDirectoryPath()); this.deleteTemporaryDirectoryOnDisconnect = options.getBoolean(TEMPORARY_DIRECTORY_DELETE_ON_DISCONNECT, TEMPORARY_DIRECTORY_DELETE_ON_DISCONNECT_DEFAULT); this.temporaryFileCreationRetries = options.getInteger(TEMPORARY_FILE_CREATION_RETRIES, TEMPORARY_FILE_CREATION_RETRIES_DEFAULT); this.temporaryFileHolderDirectoryNamePrefix = "ot-" + (new SimpleDateFormat("yyyyMMdd'T'HHmmssSSS")).format(new Date()); this.streamBufferSize = options.getInteger(REMOTE_COPY_BUFFER_SIZE, REMOTE_COPY_BUFFER_SIZE_DEFAULT); } protected void connected() { this.isConnected = true; this.openStack = new Throwable("Opened here..."); } /** * Return the OS family of the host. * * @return the OS family */ @Override public OperatingSystemFamily getHostOperatingSystem() { return os; } /** * Closes the connection. Depending on the {@link ConnectionOptions#TEMPORARY_DIRECTORY_DELETE_ON_DISCONNECT} * connection option, deletes all temporary files that have been created on the host. */ @Override public final void close() { if (!isConnected) { return; } try { if (deleteTemporaryDirectoryOnDisconnect) { deleteConnectionTemporaryDirectory(); } doClose(); closeQuietly(mapper); if (this instanceof LocalConnection) { logger.debug("Disconnected from {}", this); } else { logger.info("Disconnected from {}", this); } } finally { isConnected = false; } } /** * To be overridden by a base class to implement connection specific disconnection logic. */ protected abstract void doClose(); /** * Creates a reference to a temporary file on the host. This file has a unique name and will be automatically * removed when this connection is closed. N.B.: The file is not actually created until a put method is * invoked. * * @param prefix the prefix string to be used in generating the file's name; must be at least three characters long * @param suffix the suffix string to be used in generating the file's name; may be null, in which case * the suffix ".tmp" will be used * @return a reference to the temporary file on the host */ @Override public final OverthereFile getTempFile(String prefix, String suffix) throws RuntimeIOException { checkNotNull(prefix, "prefix is null"); if (suffix == null) { suffix = ".tmp"; } return getTempFile(prefix + suffix); } /** * Creates a reference to a temporary file on the host. This file has a unique name and will be automatically * removed when this connection is closed. N.B.: The file is not actually created until a put method is * invoked. * * @param name the name of the temporary file. May be null. * @return a reference to the temporary file on the host */ @Override public final synchronized OverthereFile getTempFile(String name) { if (name == null || name.trim().isEmpty()) { name = "tmp"; } OverthereFile temporaryDirectory = getFile(temporaryDirectoryPath); RuntimeException originalExc = null; for (int i = 0; i <= temporaryFileCreationRetries; i++) { String holderName = temporaryFileHolderDirectoryNamePrefix; if (temporaryFileHolderDirectoryNameSuffix > 0) { holderName += "." + temporaryFileHolderDirectoryNameSuffix; } OverthereFile holder = getFileForTempFile(temporaryDirectory, holderName); if (!holder.exists()) { logger.trace("Creating holder directory {} for temporary file with name {}", holder, name); try { originalExc = null; holder.mkdir(); temporaryFileHolderDirectories.add(holder); OverthereFile tempFile = holder.getFile(name); logger.debug("Generated temporary file name {}", tempFile); return tempFile; } catch(RuntimeException exc) { originalExc = exc; logger.debug(format("Failed to create holder directory %s - Trying with the next suffix", holder), exc); } } temporaryFileHolderDirectoryNameSuffix++; } if(originalExc != null) { throw new RuntimeIOException("Cannot generate a unique temporary file name on " + this, originalExc); } else { throw new RuntimeIOException("Cannot generate a unique temporary file name on " + this); } } private void deleteConnectionTemporaryDirectory() { for (OverthereFile d : temporaryFileHolderDirectories) { try { logger.info("Deleting temporary directory {}", d); d.deleteRecursively(); } catch (RuntimeException exc) { logger.warn("Got exception while deleting connection temporary directory {}. Ignoring it.", d, exc); } } } /** * Invoked by {@link #getTempFile(String)} and {@link #getTempFile(String)} to create an * {@link OverthereFile} object for a file or directory in the system or connection temporary directory. * * @param parent parent of the file to create * @param name name of the file to create. * @return the created file object */ protected abstract OverthereFile getFileForTempFile(OverthereFile parent, String name); /** * Returns the working directory. * * @return the working directory, may be null. */ @Override public OverthereFile getWorkingDirectory() { return workingDirectory; } /** * Sets the working directory in which commands are executed. If set to null, the working directory * that is used depends on the connection implementation. * * @param workingDirectory the working directory, may be null. */ @Override public void setWorkingDirectory(OverthereFile workingDirectory) { this.workingDirectory = workingDirectory; } /** * Returns the connection options used to construct this connection. * * @return the connection options. */ public ConnectionOptions getOptions() { return options; } @Override public final int execute(final CmdLine commandLine) { return execute(sysoutHandler(), syserrHandler(), commandLine); } @Override public int execute(final OverthereExecutionOutputHandler stdoutHandler, final OverthereExecutionOutputHandler stderrHandler, final CmdLine commandLine) { final OverthereProcess process = startProcess(commandLine); Thread stdoutReaderThread = null; Thread stderrReaderThread = null; final CountDownLatch latch = new CountDownLatch(2); try { stdoutReaderThread = getThread("stdout", commandLine.toString(), stdoutHandler, process.getStdout(), latch); stdoutReaderThread.start(); stderrReaderThread = getThread("stderr", commandLine.toString(), stderrHandler, process.getStderr(), latch); stderrReaderThread.start(); try { latch.await(); return process.waitFor(); } catch (InterruptedException exc) { Thread.currentThread().interrupt(); logger.info("Execution interrupted, destroying the process."); process.destroy(); throw new RuntimeIOException("Execution interrupted", exc); } } finally { quietlyJoinThread(stdoutReaderThread); quietlyJoinThread(stderrReaderThread); } } private void quietlyJoinThread(final Thread thread) { if (thread != null) { try { // interrupt the thread in case it is stuck waiting for output that will never come thread.interrupt(); thread.join(); } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } } private Thread getThread(final String streamName, final String commandLine, final OverthereExecutionOutputHandler outputHandler, final InputStream stream, final CountDownLatch latch) { Thread t = new Thread(format("%s reader", streamName)) { @Override public void run() { StringBuilder lineBuffer = new StringBuilder(); InputStreamReader stdoutReader = new InputStreamReader(stream); latch.countDown(); try { int cInt = stdoutReader.read(); while (cInt > -1) { char c = (char) cInt; outputHandler.handleChar(c); if (c != '\r' && c != '\n') { lineBuffer.append(c); } if (c == '\n') { outputHandler.handleLine(lineBuffer.toString()); lineBuffer.setLength(0); } cInt = stdoutReader.read(); } } catch (Exception exc) { logger.error(format("An exception occured reading %s while executing [%s] on %s", streamName, commandLine, this), exc); } finally { closeQuietly(stdoutReader); if (lineBuffer.length() > 0) { outputHandler.handleLine(lineBuffer.toString()); } } } }; t.setDaemon(true); return t; } /** * Executes a command with its arguments. * * @param handler the handler that will be invoked when the executed command generated output. * @param commandLine the command line to execute. * @return the exit value of the executed command. Usually 0 on successful execution. * @deprecated use {@link BaseOverthereConnection#execute(com.xebialabs.overthere.OverthereExecutionOutputHandler, com.xebialabs.overthere.OverthereExecutionOutputHandler, com.xebialabs.overthere.CmdLine)} */ @Override public final int execute(final OverthereProcessOutputHandler handler, final CmdLine commandLine) { return execute(wrapStdout(handler), wrapStderr(handler), commandLine); } /** * Starts a command with its argument and returns control to the caller. * * @param commandLine the command line to execute. * @return an object representing the executing command or null if this is not supported by the host * connection. */ @Override public OverthereProcess startProcess(CmdLine commandLine) { throw new UnsupportedOperationException("Cannot start a process on " + this); } /** * Checks whether a process can be started on this connection. * * @return true if a process can be started on this connection, false otherwise */ @Override public final boolean canStartProcess() { return canStartProcess; } /** * Subclasses MUST implement toString properly. */ @Override public abstract String toString(); @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final BaseOverthereConnection that = (BaseOverthereConnection) o; return options.equals(that.options) && protocol.equals(that.protocol); } @Override public int hashCode() { int result = protocol.hashCode(); result = 31 * result + options.hashCode(); return result; } /** * Make sure that the connection is cleaned up. This will log error messages if the connection is collected before it is cleaned up. * * @throws Throwable */ @Override protected void finalize() throws Throwable { if (isConnected) { logger.error(String.format("Connection [%s] was not closed, closing automatically.", this), openStack); closeQuietly(this); } super.finalize(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy