| /* |
| * 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.command; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.util.Collection; |
| import java.util.concurrent.Future; |
| |
| import org.apache.sshd.common.session.Session; |
| import org.apache.sshd.common.session.SessionHolder; |
| import org.apache.sshd.common.util.GenericUtils; |
| import org.apache.sshd.common.util.logging.AbstractLoggingBean; |
| import org.apache.sshd.common.util.threads.CloseableExecutorService; |
| import org.apache.sshd.common.util.threads.ExecutorServiceCarrier; |
| import org.apache.sshd.common.util.threads.ThreadUtils; |
| import org.apache.sshd.server.Environment; |
| import org.apache.sshd.server.ExitCallback; |
| import org.apache.sshd.server.channel.ChannelSession; |
| import org.apache.sshd.server.session.ServerSession; |
| import org.apache.sshd.server.session.ServerSessionAware; |
| import org.apache.sshd.server.session.ServerSessionHolder; |
| |
| /** |
| * Provides a basic useful skeleton for {@link Command} executions |
| * |
| * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a> |
| */ |
| public abstract class AbstractCommandSupport |
| extends AbstractLoggingBean |
| implements Command, Runnable, ExecutorServiceCarrier, ServerSessionAware, |
| SessionHolder<ServerSession>, ServerSessionHolder { |
| protected volatile Thread cmdRunner; |
| protected CloseableExecutorService executorService; |
| protected boolean cbCalled; |
| |
| private final String command; |
| private InputStream in; |
| private OutputStream out; |
| private OutputStream err; |
| private ExitCallback callback; |
| private Environment environment; |
| private Future<?> cmdFuture; |
| private ServerSession serverSession; |
| |
| protected AbstractCommandSupport(String command, CloseableExecutorService executorService) { |
| this.command = command; |
| |
| if (executorService == null) { |
| String poolName = GenericUtils.isEmpty(command) |
| ? getClass().getSimpleName() |
| : command.replace(' ', '_').replace('/', ':'); |
| this.executorService = ThreadUtils.newSingleThreadExecutor( |
| poolName + "-" + Math.abs(System.nanoTime() & 0xFFFF)); |
| } else { |
| this.executorService = executorService; |
| } |
| } |
| |
| public String getCommand() { |
| return command; |
| } |
| |
| @Override |
| public ServerSession getSession() { |
| return getServerSession(); |
| } |
| |
| @Override |
| public ServerSession getServerSession() { |
| return serverSession; |
| } |
| |
| @Override |
| public void setSession(ServerSession session) { |
| serverSession = session; |
| } |
| |
| @Override |
| public CloseableExecutorService getExecutorService() { |
| return executorService; |
| } |
| |
| public InputStream getInputStream() { |
| return in; |
| } |
| |
| @Override |
| public void setInputStream(InputStream in) { |
| this.in = in; |
| } |
| |
| public OutputStream getOutputStream() { |
| return out; |
| } |
| |
| @Override |
| public void setOutputStream(OutputStream out) { |
| this.out = out; |
| } |
| |
| public OutputStream getErrorStream() { |
| return err; |
| } |
| |
| @Override |
| public void setErrorStream(OutputStream err) { |
| this.err = err; |
| } |
| |
| public ExitCallback getExitCallback() { |
| return callback; |
| } |
| |
| @Override |
| public void setExitCallback(ExitCallback callback) { |
| this.callback = callback; |
| } |
| |
| public Environment getEnvironment() { |
| return environment; |
| } |
| |
| protected Future<?> getStartedCommandFuture() { |
| return cmdFuture; |
| } |
| |
| @Override |
| public void start(ChannelSession channel, Environment env) throws IOException { |
| environment = env; |
| |
| String cmd = getCommand(); |
| try { |
| if (log.isDebugEnabled()) { |
| log.debug("start({}) starting runner for command={}", channel, cmd); |
| } |
| |
| CloseableExecutorService executors = getExecutorService(); |
| cmdFuture = executors.submit(() -> { |
| cmdRunner = Thread.currentThread(); |
| this.run(); |
| }); |
| } catch (RuntimeException e) { // e.g., RejectedExecutionException |
| throw new IOException("start(" + channel + ") failed to start command " + cmd, e); |
| } |
| } |
| |
| @Override |
| public void destroy(ChannelSession channel) throws Exception { |
| // if thread has not completed, cancel it |
| boolean debugEnabled = log.isDebugEnabled(); |
| if ((cmdFuture != null) |
| && (!cmdFuture.isDone()) |
| && (cmdRunner != Thread.currentThread())) { |
| boolean result = cmdFuture.cancel(true); |
| // TODO consider waiting some reasonable (?) amount of time for cancellation |
| if (debugEnabled) { |
| log.debug("destroy({})[{}] - cancel pending future={}", channel, this, result); |
| } |
| } |
| |
| cmdFuture = null; |
| |
| CloseableExecutorService executors = getExecutorService(); |
| if ((executors != null) && (!executors.isShutdown())) { |
| Collection<Runnable> runners = executors.shutdownNow(); |
| if (debugEnabled) { |
| log.debug("destroy({})[{}] - shutdown executor service - runners count={}", |
| channel, this, runners.size()); |
| } |
| } |
| this.executorService = null; |
| } |
| |
| protected void onExit(int exitValue) { |
| onExit(exitValue, ""); |
| } |
| |
| protected void onExit(int exitValue, String exitMessage) { |
| Session session = getSession(); |
| if (cbCalled) { |
| if (log.isTraceEnabled()) { |
| log.trace("onExit({})[{}] ignore exitValue={}, message={} - already called", |
| session, this, exitValue, exitMessage); |
| } |
| return; |
| } |
| |
| ExitCallback cb = getExitCallback(); |
| try { |
| if (log.isDebugEnabled()) { |
| log.debug("onExit({})[{}] exiting - value={}, message={}", |
| session, this, exitValue, exitMessage); |
| } |
| cb.onExit(exitValue, exitMessage); |
| } finally { |
| cbCalled = true; |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return getClass().getSimpleName() + "[" + getCommand() + "]"; |
| } |
| } |