package com.novicehacks.autobot.ssh;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.atomic.AtomicBoolean;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ch.ethz.ssh2.Session;

/**
 * Single point of entry for working on SSH sessions of 3rd party SSH library.
 * 
 * @author Sharath Chand Bhaskara for NoviceHacks!
 *
 */
public final class DefaultSSHSession implements SSHSession {

	private Session session;
	private AtomicBoolean sessionClosed = new AtomicBoolean (false);
	private AtomicBoolean commandExecuted = new AtomicBoolean (false);
	private AtomicBoolean terminalRequested = new AtomicBoolean (false);
	private AtomicBoolean shellStarted = new AtomicBoolean (false);
	private InputStream remoteOutputStream;
	private InputStream remoteErrorStream;
	private OutputStream remoteInputStream;

	private Logger logger = LogManager.getLogger (DefaultSSHSession.class);

	DefaultSSHSession (Session session) {
		this.session = session;
		this.remoteOutputStream = session.getStdout ();
		this.remoteInputStream = session.getStdin ();
		this.remoteErrorStream = session.getStderr ();
	}

	@Override
	public OutputStream stdInputStream() {
		throwExceptionIfSessionClosed ();
		return this.remoteInputStream;
	}

	@Override
	public InputStream stdOutputStream() {
		throwExceptionIfSessionClosed ();
		return this.remoteOutputStream;
	}

	@Override
	public InputStream stdErrorStream() {
		throwExceptionIfSessionClosed ();
		return this.remoteErrorStream;
	}

	@Override
	public void startShell() throws IOException {
		this.logger.entry ();
		synchronized (this.shellStarted) {
			checkAndThrowExceptionIfNeeded ();
			this.session.startShell ();
			this.shellStarted.set (true);
		}
		this.logger.exit ();
	}

	@Override
	public void getTerminal() throws IOException {
		this.logger.entry ();
		synchronized (this.terminalRequested) {
			checkAndThrowExceptionIfNeeded ();
			throwIfTerminalRequestedBefore ();
			this.session.requestDumbPTY ();
			this.terminalRequested.set (true);
		}
		this.logger.exit ();
	}

	private void throwIfTerminalRequestedBefore() {
		if (this.terminalRequested.get ())
			throw new IllegalStateException ("Onle one terminal can be requested on a session");
	}

	@Override
	public void execCommand(String command) throws IOException {
		this.logger.entry ();
		synchronized (this.commandExecuted) {
			checkAndThrowExceptionIfNeeded ();
			this.session.execCommand (command);
			this.commandExecuted.set (true);
		}
		this.logger.exit ();
	}

	private void checkAndThrowExceptionIfNeeded() {
		throwExceptionIfSessionClosed ();
		throwIfCommandExecutedBefore ();
		throwIfShellStartedBefore ();
	}

	private void throwIfCommandExecutedBefore() {
		if (this.commandExecuted.get ())
			throw new IllegalStateException ("Onle one command can be executed on a session");
	}

	private void throwIfShellStartedBefore() {
		if (this.shellStarted.get ())
			throw new IllegalStateException ("Onle one shell can be started on a session");
	}

	@Override
	public void closeSession() {
		this.logger.entry ();
		throwExceptionIfSessionClosed ();
		closeIOStreams ();
		this.session.close ();
		this.sessionClosed.set (true);
		this.logger.exit ();
	}

	private void throwExceptionIfSessionClosed() {
		if (this.sessionClosed.get ())
			throw new IllegalStateException ("Session closed already");
	}

	private void closeIOStreams() {
		try {
			this.remoteErrorStream.close ();
			this.remoteInputStream.close ();
			this.remoteOutputStream.close ();
		} catch (IOException ex) {
			this.logger.warn ("Exception while closing IOStreams on session : ", ex);
		}
	}

}

@@ -165,16 +165,105 @@
 Boolean;
+%0Aimport java.util.concurrent.locks.Lock;%0Aimport java.util.concurrent.locks.ReentrantLock;
 %0A%0Aimport
@@ -865,32 +865,84 @@
 oolean (false);%0A
+%09private Lock ExecutionLock = new ReentrantLock ();%0A
 %09private InputSt
@@ -1046,16 +1046,393 @@
 tStream;
+%0A%09private static final String SingleTerminalMsg = %22Onle one terminal can be requested on a session%22;%0A%09private static final String OneCommandPerSessionMsg = %22Onle one command can be executed on a session%22;%0A%09private static final String OneShellPerSessionMsg = %22Onle one shell can be started on a session%22;%0A%09private static final String SessionClosedMsg = %22Session closed already%22;
 %0A%0A%09priva
@@ -2175,42 +2175,44 @@
 ;%0A%09%09
-synchronized (this.shellStarted) %7B
+try %7B%0A%09%09%09this.ExecutionLock.lock ();
 %0A%09%09%09
@@ -2302,32 +2302,79 @@
 ted.set (true);%0A
+%09%09%7D finally %7B%0A%09%09%09this.ExecutionLock.unlock ();%0A
 %09%09%7D%0A%09%09this.logge
@@ -2819,57 +2819,25 @@
 on (
-%22Onle one terminal can be requested on a session%22
+SingleTerminalMsg
 );%0A%09
@@ -2942,45 +2942,44 @@
 ;%0A%09%09
-synchronized (this.commandExecuted) %7B
+try %7B%0A%09%09%09this.ExecutionLock.lock ();
 %0A%09%09%09
@@ -3080,32 +3080,79 @@
 ted.set (true);%0A
+%09%09%7D finally %7B%0A%09%09%09this.ExecutionLock.unlock ();%0A
 %09%09%7D%0A%09%09this.logge
@@ -3444,55 +3444,31 @@
 on (
-%22
 On
-le one command can be executed on a s
+eCommandPerS
 ession
-%22
+Msg
 );%0A%09
@@ -3586,52 +3586,29 @@
 on (
-%22
 On
-le one shell can be started on a s
+eShellPerS
 ession
-%22
+Msg
 );%0A%09
@@ -3938,32 +3938,24 @@
 on (
-%22
 Session
- c
+C
 losed
- already%22
+Msg
 );%0A%09

Fix for the issue #12, to guard the shellstart and executecommand using a lock