/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.sshd.server.shell;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.sshd.server.Command;
import org.apache.sshd.server.Environment;
import org.apache.sshd.server.ExitCallback;

/**
 * A shell implementation that wraps an instance of {@link InvertedShell}
 * as a {@link ShellFactory.Shell}.  This is useful when using external
 * processes.
 * When starting the shell, this wrapper will also create a thread used
 * to pump the streams and also to check if the shell is alive. 
 *
 * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
 */
public class InvertedShellWrapper implements Command {

    /** default buffer size for the IO pumps. */
    public static final int DEFAULT_BUFFER_SIZE = 8192;

    private final InvertedShell shell;
    private final int bufferSize;
    private InputStream in;
    private OutputStream out;
    private OutputStream err;
    private OutputStream shellIn;
    private InputStream shellOut;
    private InputStream shellErr;
    private ExitCallback callback;
    private Thread thread;

    public InvertedShellWrapper(InvertedShell shell) {
        this(shell, DEFAULT_BUFFER_SIZE);
    }

    public InvertedShellWrapper(InvertedShell shell, int bufferSize) {
        this.shell = shell;
        this.bufferSize = bufferSize;
    }

    public void setInputStream(InputStream in) {
        this.in = in;
    }

    public void setOutputStream(OutputStream out) {
        this.out = out;
    }

    public void setErrorStream(OutputStream err) {
        this.err = err;
    }

    public void setExitCallback(ExitCallback callback) {
        this.callback = callback;
    }

    public void start(Environment env) throws IOException {
        // TODO propagate the Environment itself and support signal sending.
        shell.start(env.getEnv());
        shellIn = shell.getInputStream();
        shellOut = shell.getOutputStream();
        shellErr = shell.getErrorStream();
        thread = new Thread("inverted-shell-pump") {
            @Override
            public void run() {
                pumpStreams();
            }
        };
        thread.start();
    }

    public void destroy() {
        shell.destroy();
    }

    protected void pumpStreams() {
        try {
            // Use a single thread to correctly sequence the output and error streams.
            // If any bytes are available from the output stream, send them first, then
            // check the error stream, or wait until more data is available.
            byte[] buffer = new byte[bufferSize];
            for (;;) {
                if (!shell.isAlive()) {
                    callback.onExit(shell.exitValue());
                    return;
                }
                if (pumpStream(in, shellIn, buffer)) {
                    continue;
                }
                if (pumpStream(shellOut, out, buffer)) {
                    continue;
                }
                if (pumpStream(shellErr, err, buffer)) {
                    continue;
                }
                // Sleep a bit.  This is not very good, as it consumes CPU, but the
                // input streams are not selectable for nio, and any other blocking
                // method would consume at least two threads
                Thread.sleep(1);
            }
        } catch (Exception e) {
            shell.destroy();
            callback.onExit(shell.exitValue());
        }
    }

    private boolean pumpStream(InputStream in, OutputStream out, byte[] buffer) throws IOException {
        if (in.available() > 0) {
            int len = in.read(buffer);
            if (len > 0) {
                out.write(buffer, 0, len);
                out.flush();
                return true;
            }
        }
        return false;
    }

}

@@ -925,16 +925,145 @@
 Stream;%0A
+import java.util.concurrent.Executor;%0Aimport java.util.concurrent.Executors;%0A%0Aimport org.apache.mina.util.NamePreservingRunnable;
 %0Aimport 
@@ -1764,24 +1764,61 @@
 hell shell;%0A
+    private final Executor executor;%0A
     private 
@@ -2067,35 +2067,8 @@
 ack;
-%0A    private Thread thread;
 %0A%0A  
@@ -2144,134 +2144,505 @@
 ll, 
-DEFAULT_BUFFER_SIZE);%0A    %7D%0A%0A    public InvertedShellWrapper(InvertedShell shell, int bufferSize) %7B%0A        this.shell = shell
+Executors.newSingleThreadExecutor(), DEFAULT_BUFFER_SIZE);%0A    %7D%0A%0A    public InvertedShellWrapper(InvertedShell shell, Executor executor) %7B%0A        this(shell, executor, DEFAULT_BUFFER_SIZE);%0A    %7D%0A%0A    public InvertedShellWrapper(InvertedShell shell, int bufferSize) %7B%0A        this(shell, Executors.newSingleThreadExecutor(), bufferSize);%0A    %7D%0A%0A    public InvertedShellWrapper(InvertedShell shell, Executor executor, int bufferSize) %7B%0A        this.shell = shell;%0A        this.executor = executor
 ;%0A  
@@ -3338,74 +3338,68 @@
     
-thread = new Thread(%22inverted-shell-pump%22) %7B%0A            @Override
+executor.execute(new NamePreservingRunnable(new Runnable() %7B
 %0A   
@@ -3485,31 +3485,32 @@
    %7D
-;%0A        thread.start(
+, %22inverted-shell-pump%22)
 );%0A 

Allow the configuration of InvertedShellWrapper's thread pool