/********************************************************* begin of preamble
**
** Copyright (C) 2003-2010 Software- und Organisations-Service GmbH. 
** All rights reserved.
**
** This file may be used under the terms of either the 
**
**   GNU General Public License version 2.0 (GPL)
**
**   as published by the Free Software Foundation
**   http://www.gnu.org/licenses/gpl-2.0.txt and appearing in the file
**   LICENSE.GPL included in the packaging of this file. 
**
** or the
**  
**   Agreement for Purchase and Licensing
**
**   as offered by Software- und Organisations-Service GmbH
**   in the respective terms of supply that ship with this file.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
** IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
** THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
** PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
** BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
********************************************************** end of preamble*/
package com.sos.VirtualFileSystem.SSH;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Vector;
import org.apache.log4j.Logger;
import ch.ethz.ssh2.ChannelCondition;
import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.HTTPProxyData;
import ch.ethz.ssh2.SFTPException;
import ch.ethz.ssh2.SFTPv3Client;
import ch.ethz.ssh2.SFTPv3FileAttributes;
import ch.ethz.ssh2.SFTPv3FileHandle;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
import com.sos.JSHelper.Basics.JSJobUtilities;
import com.sos.VirtualFileSystem.DataElements.SOSFileList;
import com.sos.VirtualFileSystem.DataElements.SOSFolderName;
import com.sos.VirtualFileSystem.Interfaces.ISOSAuthenticationOptions;
import com.sos.VirtualFileSystem.Interfaces.ISOSConnection;
import com.sos.VirtualFileSystem.Interfaces.ISOSConnectionOptions;
import com.sos.VirtualFileSystem.Interfaces.ISOSSession;
import com.sos.VirtualFileSystem.Interfaces.ISOSShell;
import com.sos.VirtualFileSystem.Interfaces.ISOSShellOptions;
import com.sos.VirtualFileSystem.Interfaces.ISOSVFSHandler;
import com.sos.VirtualFileSystem.Interfaces.ISOSVirtualFileSystem;
import com.sos.VirtualFileSystem.Interfaces.ISOSVirtualFolder;
import com.sos.VirtualFileSystem.Options.SOSConnection2OptionsAlternate;
import com.sos.VirtualFileSystem.common.SOSVfsBaseClass;
import com.sos.i18n.annotation.I18NResourceBundle;
/**
* \class SOSSSH2GanymedImpl 
* 
* \brief SOSSSH2GanymedImpl - 
* 
* \details
*
* \section SOSSSH2GanymedImpl.java_intro_sec Introduction
*
* \section SOSSSH2GanymedImpl.java_samples Some Samples
*
* \code
*   .... code goes here ...
* \endcode
*
* <p style="text-align:center">
* <br />---------------------------------------------------------------------------
* <br /> APL/Software GmbH - Berlin
* <br />##### generated by ClaviusXPress (http://www.sos-berlin.com) #########
* <br />---------------------------------------------------------------------------
* </p>
* \author KB
* @version $Id: SOSSSH2GanymedImpl.java 14789 2011-07-08 15:51:52Z sos $16.05.2010
* \see reference
*
* Created on 16.05.2010 19:17:53
 */
@I18NResourceBundle(baseName = "SOSVirtualFileSystem", defaultLocale = "en")
public class SOSSSH2GanymedImpl extends SOSVfsBaseClass implements JSJobUtilities, ISOSShell, ISOSVFSHandler, ISOSVirtualFileSystem, ISOSConnection, ISOSSession {
  private final String  conClassName  = "SOSSSH2GanymedImpl";
  private Logger      logger      = Logger.getLogger(SOSSSH2GanymedImpl.class);
  private JSJobUtilities objJSJobUtilities = this;
  
  public SOSSSH2GanymedImpl() {
    //
  }
  private ISOSConnectionOptions    objCO          = null;
  private ISOSAuthenticationOptions  objAO          = null;
  private ISOSShellOptions      objSO          = null;
  boolean                isAuthenticated      = false;
  boolean                isConnected        = false;
  /** Line currently being displayed on the shell **/
  protected String          strCurrentLine      = "";
  /** ssh connection object */
  protected Connection        sshConnection      = null;
  /** ssh session object */
  protected Session          sshSession        = null;
  /** Inputstreams for stdout and stderr **/
  protected InputStream        ipsStdOut;
  protected InputStream        ipsStdErr;
  private boolean            flgIsRemoteOSWindows  = false;
  private SFTPv3Client        sftpClient        = null;
  private RemoteConsumer        stdoutConsumer      = null;
  private RemoteConsumer        stderrConsumer      = null;
  /** Output from stdout and stderr **/
  protected StringBuffer        strbStdoutOutput = new StringBuffer();
  protected StringBuffer        strbStderrOutput = new StringBuffer();
  /** timestamp of the last text from stdin or stderr **/
  protected long            lngLastTime        = 0;
  private OutputStream        stdin;
  private OutputStreamWriter      stdinWriter        = null;
  private Integer            intExitStatus        = null;
  private String            strExitSignal        = null;
  private Vector<String>        vecFilesToDelete    = new Vector<String>();
  private Vector<String> getFilesToDelete() {
    if (vecFilesToDelete == null) {
      vecFilesToDelete = new Vector<String>();
    }
    return vecFilesToDelete;
  }
  public ISOSConnection Connect(final String pstrHostName, final int pintPortNumber) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::Connect";
    try {
      isConnected = false;
      this.setSshConnection(new Connection(pstrHostName, pintPortNumber));
    }
    catch (Exception e) {
      if (this.getSshConnection() != null)
        try {
          this.getSshConnection().close();
          this.setSshConnection(null);
        }
        catch (Exception ex) {
        }
      throw new Exception(e.getMessage());
    }
    return this;
  }
  /**
   * 
   * \brief Connect
   * 
   * \details
   *
   * \return 
   *
   * @return
   * @throws Exception
   */
  public ISOSConnection Connect() throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::Connect";
    try {
      isConnected = false;
      this.setSshConnection(new Connection(objCO.getHost().Value(), objCO.getPort().value()));
      if (objCO.getProxy_host().IsNotEmpty()) {
        HTTPProxyData objProxy = null;
        if (objCO.getProxy_user().IsEmpty()) {
          objProxy = new HTTPProxyData(objCO.getProxy_host().Value(), objCO.getProxy_port().value());
        }
        else {
          objProxy = new HTTPProxyData(objCO.getProxy_host().Value(), objCO.getProxy_port().value(), objCO.getProxy_user().Value(),
              objCO.getProxy_password().Value());
        }
        this.getSshConnection().setProxyData(objProxy);
      }
      this.getSshConnection().connect();
      isConnected = true;
      logger.debug(String.format("connected to %1$s using Port %2$d", objCO.getHost().Value(), objCO.getPort().value()));
    }
    catch (Exception e) {
      try {
        this.setSshConnection(null);
      }
      catch (Exception ex) {
      }
      throw e;
    }
    return this;
  }
  /**
   * Check existence of a file or directory
   * 
   * @param psftpClient
   * @param filename
   * @return true, if file exists
   * @throws Exception
   */
  protected boolean sshFileExists(SFTPv3Client psftpClient, String filename) {
    try {
      SFTPv3FileAttributes attributes = psftpClient.stat(filename);
      if (attributes != null) {
        return (attributes.isRegularFile() || attributes.isDirectory());
      }
      else {
        return false;
      }
    }
    catch (Exception e) {
      return false;
    }
  }
  /**
   * Checks if file is a directory
   * 
   * @param psftpClient
   * @param filename
   * @return true, if filename is a directory
   */
  protected boolean isDirectory(SFTPv3Client psftpClient, String filename) {
    try {
      return psftpClient.stat(filename).isDirectory();
    }
    catch (Exception e) {
    }
    return false;
  }
  /**
   * Returns the file size of a file
   * 
   * @param sftpClient
   * @param filename
   * @return the size of the file
   * @throws Exception
   */
  protected long getFileSize(SFTPv3Client psftpClient, String filename) throws Exception {
    return psftpClient.stat(filename).size.longValue();
  }
  /**
   * Check existence of a file or directory
   * 
   * @param sftpClient
   * @param filename
   * @return integer representation of file permissions
   * @throws Exception
   */
  protected int sshFilePermissions(SFTPv3Client psftpClient, String filename) {
    try {
      SFTPv3FileAttributes attributes = psftpClient.stat(filename);
      if (attributes != null) {
        return attributes.permissions.intValue();
      }
      else {
        return 0;
      }
    }
    catch (Exception e) {
      return 0;
    }
  }
  /**
   * normalize / to \ and remove trailing slashes from a path
   * 
   * @param path
   * @return normalized path
   * @throws Exception
   */
  @SuppressWarnings("unused")
  private String normalizePath(String path) throws Exception {
    String normalizedPath = path.replaceAll("\\\\", "/");
    while (normalizedPath.endsWith("\\") || normalizedPath.endsWith("/")) {
      normalizedPath = normalizedPath.substring(0, normalizedPath.length() - 1);
    }
    return normalizedPath;
  }
  /**
   * @return Returns the sshConnection.
   */
  protected Connection getSshConnection() {
    return sshConnection;
  }
  protected void setSshConnection(Connection psshConnection) {
    if (psshConnection == null) {
      isConnected = false;
      if (sftpClient != null) {
        if (this.getFilesToDelete() != null) {
          for (String strFileNameToDelete : vecFilesToDelete) {
            try {
              this.deleteFile(strFileNameToDelete);
            }
            catch (Exception e) {
              e.printStackTrace();
            }
          }
          vecFilesToDelete = null;
        }
        sftpClient.close();
        sftpClient = null;
        logger.debug("sftpClient closed.");
      }
      if (stderrConsumer != null) {
        stderrConsumer.end();
        stderrConsumer = null;
        logger.debug("stderrConsumer closed.");
      }
      if (stdoutConsumer != null) {
        stdoutConsumer.end();
        stdoutConsumer = null;
        logger.debug("stdoutConsumer closed.");
      }
      if (sshSession != null) {
        sshSession.close();
        sshSession = null;
        logger.debug("sshSession closed.");
      }
      if (this.sshConnection != null) {
        sshConnection.close();
        logger.debug("sshConnection closed.");
      }
    }
    this.sshConnection = psshConnection;
  }
  /**
   * @return Returns the sshSession.
   */
  public Session getSshSession() {
    return sshSession;
  }
  /**
   * @param sshSession The sshSession to set.
   */
  public void setSshSession(Session psshSession) {
    this.sshSession = psshSession;
  }
  /**
   * 
   * \brief createCommandScript
   * 
   * \details
   *
   * \return File
   *
   * @param isWindows
   * @return
   * @throws Exception
   */
  public String createScriptFile(final String pstrContent) throws Exception {
    try {
      String commandScript = pstrContent;
//      logger.info("pstrContent = " + pstrContent);
      if (flgIsRemoteOSWindows == false) {
        commandScript = commandScript.replaceAll("(?m)\r", "");
      }
      logger.info("commandScript = " + pstrContent);
      commandScript = objJSJobUtilities.replaceSchedulerVars(flgIsRemoteOSWindows, commandScript);
      String suffix = (flgIsRemoteOSWindows ? ".cmd" : ".sh");
      File resultFile = File.createTempFile("sos", suffix);
      BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(resultFile)));
      out.write(commandScript);
      out.flush();
      out.close();
      resultFile.deleteOnExit();
      putFile(resultFile);
      String strFileName2Return = resultFile.getName();
      if (flgIsRemoteOSWindows == false) {
        strFileName2Return = "./" + strFileName2Return;
      }
      this.getFilesToDelete().add(strFileName2Return);
      return strFileName2Return;
    }
    catch (Exception e) {
      e.printStackTrace();
      throw e;
    }
  }
  @Override
  public ISOSConnection getConnection() {
    return this;
  }
  @Override
  public ISOSSession getSession() {
    return this;
  }
  @Override
  public ISOSConnection Connect(ISOSConnectionOptions pobjConnectionOptions) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::Connect";
    this.objCO = pobjConnectionOptions;
    if (objCO != null) {
      this.Connect();
    }
    return this;
  }
  @Override
  public void CloseConnection() throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::CloseConnection";
    this.setSshConnection(null);
  }
  /**
   * 
   * \brief Authenticate
   * 
   * \details
   *
   * \return 
   *
   * @param pobjAO
   * @return
   * @throws Exception
   */
  @Override
  public ISOSConnection Authenticate(ISOSAuthenticationOptions pobjAO) throws Exception {
    final String conMethodName = conClassName + "::Authenticate";
    if (pobjAO == null) {
      throw new Exception (conClassName + ": Instance for Options is null but mandatory");
    }
    objAO = pobjAO;
    String strUserID = objAO.getUser().Value();
    String strPW = objAO.getPassword().Value();
    if (objAO.getAuth_method().isPublicKey()) {
      objAO.getAuth_file().CheckMandatory(true);
      File authenticationFile = objAO.getAuth_file().JSFile();
      isAuthenticated = getSshConnection().authenticateWithPublicKey(strUserID, authenticationFile, strPW);
    }
    else
      if (objAO.getAuth_method().isPassword()) {
        isAuthenticated = getSshConnection().authenticateWithPassword(strUserID, strPW);
      }
    if (isAuthenticated == false) {
      throw new Exception(conMethodName + ": " + "authentication failed " + objAO.toString());
    }
    logger.info(String.format("user %1$s logged in", strUserID));
    return this;
  }
  @Override
  public ISOSVFSHandler getHandler() {
    return this;
  }
  /**
   * 
   * \brief remoteIsWindowsShell
   * 
   * \details
   *
   * \return boolean
   *
   * @return
   */
  public boolean remoteIsWindowsShell() {
    Session objSSHSession = null;
    flgIsRemoteOSWindows = false;
    try {
      // TODO the testcommand should be defined by an option
      String checkShellCommand = "echo %ComSpec%";
      logger.debug("Opening new session...");
      objSSHSession = this.getSshConnection().openSession();
      logger.debug("Executing command " + checkShellCommand);
      objSSHSession.execCommand(checkShellCommand);
      logger.debug("output to stdout for remote command: " + checkShellCommand);
      ipsStdOut = new StreamGobbler(objSSHSession.getStdout());
      ipsStdErr = new StreamGobbler(objSSHSession.getStderr());
      BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(ipsStdOut));
      String stdOut = "";
      while (true) {
        String line = stdoutReader.readLine();
        if (line == null)
          break;
        logger.debug(line);
        stdOut += line;
      }
      logger.debug("output to stderr for remote command: " + checkShellCommand);
      BufferedReader stderrReader = new BufferedReader(new InputStreamReader(ipsStdErr));
      while (true) {
        String line = stderrReader.readLine();
        if (line == null)
          break;
        logger.debug(line);
      }
      // TODO The expected result-string for testing the os should be defined by an option
      if (stdOut.indexOf("cmd.exe") > -1) {
        logger.debug("Remote shell is a Windows shell.");
        flgIsRemoteOSWindows = true;
        return true;
      }
      else {
        logger.debug("Remote shell is a Linux/Unix shell.");
      }
    }
    catch (Exception e) {
      logger.debug("Failed to check if remote system is windows shell: " + e);
    }
    finally {
      if (objSSHSession != null)
        try {
          objSSHSession.close();
        }
        catch (Exception e) {
          logger.debug("Failed to close session: ", e);
        }
    }
    return false;
  }
  /**
   * 
   * \brief transferCommandScript
   * 
   * \details
   *
   * \return File
   *
   * @param pfleCommandFile
   * @param isWindows
   * @return
   * @throws Exception
   */
  public void putFile(File pfleCommandFile) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::putFile";
    String suffix = (flgIsRemoteOSWindows ? ".cmd" : ".sh");
    String strFileName = pfleCommandFile.getName();
    try {
      boolean exists = true;
      // check if File already exists
      while (exists) {
        try {
          FtpClient().stat(strFileName);
        }
        catch (SFTPException e) {
          logger.debug("Error code when checking file existence: " + e.getServerErrorCode());
          exists = false;
        }
        if (exists) {
          logger.debug("file with that name already exists, trying another name...");
          /**
           * \todo
           * TODO Tempfile-NamePrefix variable via options
           */
          File resultFile = File.createTempFile("sos", suffix);
          resultFile.delete();
          pfleCommandFile.renameTo(resultFile);
          pfleCommandFile = resultFile;
        }
      }
      // set execute permissions for owner
      SFTPv3FileHandle fileHandle = this.getFileHandle(strFileName, new Integer(0700));
      FileInputStream fis = null;
      long offset = 0;
      try {
        fis = new FileInputStream(pfleCommandFile);
        // TODO BufferSize as an Option 
        byte[] buffer = new byte[1024];
        while (true) {
          int len = fis.read(buffer, 0, buffer.length);
          if (len <= 0)
            break;
          sftpClient.write(fileHandle, offset, buffer, 0, len);
          offset += len;
        }
        fis.close();
        fis = null;
      }
      catch (Exception e) {
        e.printStackTrace();
        throw e;
      }
      finally {
        if (fis != null)
          try {
            fis.close();
            fis = null;
          }
          catch (Exception ex) {
          }
      }
      logger.debug("canonical path: " + sftpClient.canonicalPath(strFileName));
      sftpClient.closeFile(fileHandle);
      fileHandle = null;
    }
    catch (Exception e) {
      e.printStackTrace();
      throw e;
    }
  }
  public SFTPv3FileHandle getFileHandle(final String pstrFileName, final Integer pintPermissions) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::setFilePermissions";
    SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
    attr.permissions = pintPermissions;
    SFTPv3FileHandle fileHandle = this.FtpClient().createFileTruncate(pstrFileName, attr);
    return fileHandle;
  } // private void setFilePermissions
  public void setFilePermissions(final String pstrFileName, final Integer pintPermissions) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::setFilePermissions";
    SFTPv3FileAttributes attr = new SFTPv3FileAttributes();
    attr.permissions = pintPermissions;
    @SuppressWarnings("unused")
    SFTPv3FileHandle fileHandle = this.FtpClient().createFileTruncate(pstrFileName, attr);
  } // private void setFilePermissions
//  @Override
//  public ISOSVirtualFile getFile(String pstrFileName) throws Exception {
//    // TODO Auto-generated method stub
//    return null;
//  }
  /**
   * 
   * \brief deleteCommandScript
   * 
   * \details
   *
   * \return void
   *
   * @param pstrCommandFile
   * @throws Exception
   */
  public void deleteFile(String pstrCommandFile) throws Exception {
    try {
      if (isNotEmpty(pstrCommandFile)) {
        this.FtpClient().rm(pstrCommandFile);
        logger.debug("File deleted : " + pstrCommandFile);
      }
    }
    catch (Exception e) {
      logger.error("Failed to delete remote command script: ", e);
    }
  }
  private SFTPv3Client FtpClient() throws Exception {
    if (sftpClient == null) {
      sftpClient = new SFTPv3Client(this.getSshConnection());
    }
    return sftpClient;
  }
  public void CloseSession() throws Exception {
  }
  /**
   * 
   * \brief OpenSession
   * 
   * \details
   *
   * \return 
   *
   * @param pobjShellOptions
   * @return
   * @throws Exception
   */
  public ISOSSession OpenSession(ISOSShellOptions pobjShellOptions) {
    this.objSO = pobjShellOptions;
    if (objSO == null) {
      throw new RuntimeException("no shell-options specified but mandatory");
    }
    try {
      this.setSshSession(this.getSshConnection().openSession());
      if (objSO.getSimulate_shell().value() == true) {
        long loginTimeout = objSO.getSimulate_shell_login_timeout().value();
        String strPromptTrigger = objSO.getSimulate_shell_prompt_trigger().Value();
        logger.debug("Requesting PTY...");
        this.getSshSession().requestDumbPTY();
        logger.debug("Starting shell...");
        this.getSshSession().startShell();
        ipsStdOut = getSshSession().getStdout();
        ipsStdErr = getSshSession().getStderr();
        stdoutConsumer = new RemoteConsumer(getStdOutBuffer(), true, ipsStdOut);
        stderrConsumer = new RemoteConsumer(getStdErrBuffer(), false, ipsStdErr);
        stdoutConsumer.start();
        stderrConsumer.start();
        stdin = getSshSession().getStdin();
        stdinWriter = new OutputStreamWriter(stdin);
        logger.debug("Waiting for login prompt...");
        boolean loggedIn = false;
        while (loggedIn == false) {
          if (lngLastTime > 0) {
            loggedIn = Check4TimeOutOrPrompt(loginTimeout, strPromptTrigger);
          }
        }
      }
      else {
        if (objSO.getIgnore_hangup_signal().value() == false) {
          sshSession.requestPTY("vt100");
        }
      }
    }
    catch (Exception e) {
      throw new RuntimeException(e);
    }
    return this;
  }
  /**
   * 
   * \brief Check4TimeOutOrPrompt
   * 
   * \details
   *
   * \return boolean
   *
   * @param plngTimeOut
   * @param pstrPromptTrigger
   * @return
   */
  private boolean Check4TimeOutOrPrompt(final long plngTimeOut, final String pstrPromptTrigger) {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::Check4TimeOutOrPrompt";
    long currentTimeMillis = System.currentTimeMillis();
    if (plngTimeOut > 0 && lngLastTime + plngTimeOut < currentTimeMillis) {// kommt nichts mehr
      return true;
    }
    if (pstrPromptTrigger.length() > 0 && strCurrentLine.indexOf(pstrPromptTrigger) != -1) {
      logger.debug("Found login prompt " + pstrPromptTrigger);
      strCurrentLine = "";
      return true;
    }
    return false;
  } // private boolean Check4TimeOutOrPrompt
  /**
   * 
   * \brief ExecuteCommand
   * 
   * \details
   * A single command is executed by this method.
   * a command can be executed in a "simulated"-shell or as a single ssh-session.
   * 
   * if it is executed as a single ssh-session the method must wait until the
   * ssh-server was sent an EOF signal, because to get the exit-code and exit-status of
   * the command is not poossible.
   * 
   * @param pstrCmd
   * @throws Exception
   */
  public void ExecuteCommand(final String pstrCmd) throws Exception {
    @SuppressWarnings("unused")
    final String conMethodName = conClassName + "::ExecuteCommand";
    intExitStatus = null;
    strExitSignal = null;
    String strCmd = pstrCmd;
    if (objSO.getSimulate_shell().value() == true) {
      long lngInactivityTimeout = objSO.getSimulate_shell_inactivity_timeout().value();
      String strPromptTrigger = objSO.getSimulate_shell_prompt_trigger().Value();
      stdinWriter.write(strCmd + "\n");
      stdinWriter.flush();
      boolean prompt = false;
      while (prompt == false) {
        prompt = Check4TimeOutOrPrompt(lngInactivityTimeout, strPromptTrigger);
      }
      strCurrentLine = "";
      logger.debug("output to stdout for remote command: " + strCmd);
      logger.debug(getStdOutBuffer().toString());
      strbStdoutOutput = new StringBuffer();
    }
    else {
      if (flgIsRemoteOSWindows == false) {
        strCmd = "echo $$ && " + strCmd;
      }
      this.getSshSession().execCommand(strCmd);
      logger.info("output to stdout for remote command: " + strCmd);
      ipsStdOut = new StreamGobbler(this.getSshSession().getStdout());
      ipsStdErr = new StreamGobbler(this.getSshSession().getStderr());
      BufferedReader stdoutReader = new BufferedReader(new InputStreamReader(ipsStdOut));
      strbStdoutOutput = new StringBuffer();
      while (true) {
        String line = stdoutReader.readLine();
        if (line == null)
          break;
        logger.info(line);
        getStdOutBuffer().append(line + "\n");
      }
      logger.info("output to stderr for remote command: " + strCmd);
      BufferedReader stderrReader = new BufferedReader(new InputStreamReader(ipsStdErr));
      strbStderrOutput = new StringBuffer();
      while (true) {
        String line = stderrReader.readLine();
        if (line == null)
          break;
        logger.info(line);
        strbStderrOutput.append(line + "\n");
      }
      /**
       * give the command (e.g. session) some time to end.
       * otherwise it is not possible to get valid exit-infos.
       */
      // TODO waitForCondition as an Option
      @SuppressWarnings("unused")
      int res = getSshSession().waitForCondition(ChannelCondition.EOF, 30 * 1000);
    } //     if (objSO.getSimulate_shell().value() == true)
    String strWhatSignal = "";
    try {
      strWhatSignal = "exit status"; 
      intExitStatus = this.getSshSession().getExitStatus();
      strWhatSignal = "exit signal"; 
      strExitSignal = this.getSshSession().getExitSignal();
    }
    catch (Exception e) {
      logger.info(String.format("could not retrieve %1$s, possibly not supported by remote ssh server", strWhatSignal));
    }
  }
  @Override
  public Integer getExitCode() {
    return intExitStatus;
  }
  @Override
  public String getExitSignal() {
    return strExitSignal;
  }
//  @Override
  public StringBuffer getStdErrBuffer() throws Exception {
    if (strbStderrOutput == null) {
      strbStderrOutput = new StringBuffer();
    }
    return strbStderrOutput;
  }
//  @Override
  public StringBuffer getStdOutBuffer() throws Exception {
    if (strbStdoutOutput == null) {
      strbStdoutOutput = new StringBuffer();
    }
    return strbStdoutOutput;
  }
  /**
   * This thread consumes output from the remote server puts it into fields of
   * the main class
   */
  class RemoteConsumer extends Thread {
    private StringBuffer  sbuf;
    private boolean      writeCurrentline  = false;
    private InputStream    stream;
    boolean          end          = false;
    RemoteConsumer(StringBuffer buffer, boolean writeCurr, InputStream str) {
      if (buffer == null) {
        buffer = new StringBuffer();
      }
      this.sbuf = buffer;
      this.writeCurrentline = true;
      this.stream = str;
    }
    /**
     * 
     * \brief addText
     * 
     * \details
     *
     * \return void
     *
     * @param data
     * @param len
     */
    private void addText(byte[] data, int len) {
      lngLastTime = System.currentTimeMillis();
      String outstring = new String(data).substring(0, len);
      sbuf.append(outstring);
      if (writeCurrentline) {
        int newlineIndex = outstring.indexOf("\n");
        if (newlineIndex > -1) {
          String stringAfterNewline = outstring.substring(newlineIndex);
          strCurrentLine = stringAfterNewline;
        }
        else {
          strCurrentLine += outstring;
        }
      }
    }
    /**
     * 
     * \brief run
     * 
     * \details
     *
     * \return 
     *
     */
    public void run() {
      byte[] buff = new byte[64];
      try {
        while (!end) {
          buff = new byte[8];
          int len = stream.read(buff);
          if (len == -1)
            return;
          addText(buff, len);
        }
      }
      catch (Exception e) {
      }
    }
    /**
     * 
     * \brief end
     * 
     * \details
     *
     * \return void
     *
     */
    public synchronized void end() {
      end = true;
    }
  } // RemoteConsumer
//  @Override
//  public boolean FileExists(String filename) throws Exception {
//
//    return this.sshFileExists(this.FtpClient(), filename);
//  }
//
//  @Override
//  public Integer getFilePermissions(final String pstrFileName) throws Exception {
//    return this.sshFilePermissions(this.FtpClient(), pstrFileName);
//  }
//
//  @Override
//  public long getFileSize(final String pstrFileName) throws Exception {
//
//    return this.getFileSize(this.FtpClient(), pstrFileName);
//  }
//
//  @Override
//  public boolean isDirectory(final String pstrFileName) throws Exception {
//
//    return this.isDirectory(this.FtpClient(), pstrFileName);
//  }
//
//  @Override
//  public void putFile(String pstrFileName) throws Exception {
//    this.putFile(new File(pstrFileName));
//  }
  @Override
  public SOSFileList dir(SOSFolderName pobjFolderName) {
    // TODO Auto-generated method stub
    return null;
  }
//  @Override
//  public SOSFileList dir() {
//    // TODO Auto-generated method stub
//    return null;
//  }
  @Override
  public ISOSVirtualFolder mkdir(SOSFolderName pobjFolderName) throws IOException {
    // TODO Auto-generated method stub
    return null;
  }
  @Override
  public boolean rmdir(SOSFolderName pobjFolderName) throws IOException {
    // TODO Auto-generated method stub
    return false;
  }
  @Override
  public String myReplaceAll(String source, String what, String replacement) {
    return source;
  }
  @Override
  public String replaceSchedulerVars(boolean isWindows, String pstrString2Modify) {
    logger.debug("replaceSchedulerVars as Dummy-call executed. No Instance of JobUtilites specified.");
    return pstrString2Modify;
  }
  @Override
  public void setJSParam(String pstrKey, String pstrValue) {
    
  }
//  @Override
//  public void setParameters(Variable_set pVariableSet) {
//    
//  }
  
  @Override
  public void setJSJobUtilites (JSJobUtilities pobjJSJobUtilities) {
    if (pobjJSJobUtilities == null) {
      objJSJobUtilities = this;
    }
    else {
      objJSJobUtilities = pobjJSJobUtilities;
    }
    logger.debug("objJSJobUtilities = " + objJSJobUtilities.getClass().getName());
  }
  @Override
  public void setJSParam(String pstrKey, StringBuffer pstrValue) {
    setJSParam(pstrKey, pstrValue.toString());
    
  }
  @Override
  public StringBuffer getStdErr() throws Exception {
    return getStdErrBuffer();
  }
  @Override
  public StringBuffer getStdOut() throws Exception {
    return getStdOutBuffer();
  }
//  @Override
//  public ISOSVirtualFile TransferMode(SOSOptionTransferMode pobjFileTransferMode) throws Exception {
//    // TODO Auto-generated method stub
//    return null;
//  }
  @Override
  public SOSFileList dir(String pathname, int flag) {
    // TODO Auto-generated method stub
    return null;
  }
  @Override
  public ISOSConnection Connect(SOSConnection2OptionsAlternate pobjConnectionOptions) throws Exception {
    // TODO Auto-generated method stub
    return null;
  }
  @Override
  public String getCurrentNodeName() {
    return "";
  }
  
}