/*
* 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.scp;
import java.util.Collection;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import org.apache.sshd.common.scp.ScpFileOpener;
import org.apache.sshd.common.scp.ScpFileOpenerHolder;
import org.apache.sshd.common.scp.ScpHelper;
import org.apache.sshd.common.scp.ScpTransferEventListener;
import org.apache.sshd.common.util.EventListenerUtils;
import org.apache.sshd.common.util.ObjectBuilder;
import org.apache.sshd.common.util.threads.ExecutorServiceConfigurer;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.CommandFactory;
/**
* This <code>CommandFactory</code> can be used as a standalone command factory
* or can be used to augment another <code>CommandFactory</code> and provides
* <code>SCP</code> support.
*
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
* @see ScpCommand
*/
public class ScpCommandFactory implements ScpFileOpenerHolder, CommandFactory, Cloneable, ExecutorServiceConfigurer {
/**
* A useful {@link ObjectBuilder} for {@link ScpCommandFactory}
*/
public static class Builder implements ObjectBuilder<ScpCommandFactory> {
private final ScpCommandFactory factory = new ScpCommandFactory();
public Builder() {
super();
}
public Builder withFileOpener(ScpFileOpener opener) {
factory.setScpFileOpener(opener);
return this;
}
public Builder withDelegate(CommandFactory delegate) {
factory.setDelegateCommandFactory(delegate);
return this;
}
public Builder withExecutorService(ExecutorService service) {
factory.setExecutorService(service);
return this;
}
public Builder withShutdownOnExit(boolean shutdown) {
factory.setShutdownOnExit(shutdown);
return this;
}
public Builder withSendBufferSize(int sendSize) {
factory.setSendBufferSize(sendSize);
return this;
}
public Builder withReceiveBufferSize(int receiveSize) {
factory.setReceiveBufferSize(receiveSize);
return this;
}
public Builder addEventListener(ScpTransferEventListener listener) {
factory.addEventListener(listener);
return this;
}
public Builder removeEventListener(ScpTransferEventListener listener) {
factory.removeEventListener(listener);
return this;
}
@Override
public ScpCommandFactory build() {
return factory.clone();
}
}
/*
* NOTE: we expose setters since there is no problem to change these settings between
* successive invocations of the 'createCommand' method
*/
private CommandFactory delegate;
private ExecutorService executors;
private boolean shutdownExecutor;
private ScpFileOpener fileOpener;
private int sendBufferSize = ScpHelper.MIN_SEND_BUFFER_SIZE;
private int receiveBufferSize = ScpHelper.MIN_RECEIVE_BUFFER_SIZE;
private Collection<ScpTransferEventListener> listeners = new CopyOnWriteArraySet<>();
private ScpTransferEventListener listenerProxy;
public ScpCommandFactory() {
listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), listeners);
}
@Override
public ScpFileOpener getScpFileOpener() {
return fileOpener;
}
@Override
public void setScpFileOpener(ScpFileOpener fileOpener) {
this.fileOpener = fileOpener;
}
public CommandFactory getDelegateCommandFactory() {
return delegate;
}
/**
* @param factory A {@link CommandFactory} to be used if the
* command is not an SCP one. If {@code null} then an {@link IllegalArgumentException}
* will be thrown when attempting to invoke {@link #createCommand(String)}
* with a non-SCP command
*/
public void setDelegateCommandFactory(CommandFactory factory) {
delegate = factory;
}
@Override
public ExecutorService getExecutorService() {
return executors;
}
/**
* @param service An {@link ExecutorService} to be used when
* starting {@link ScpCommand} execution. If {@code null} then a single-threaded
* ad-hoc service is used. <B>Note:</B> the service will <U>not</U> be shutdown
* when the command is terminated - unless it is the ad-hoc service, which will be
* shutdown regardless
*/
@Override
public void setExecutorService(ExecutorService service) {
executors = service;
}
@Override
public boolean isShutdownOnExit() {
return shutdownExecutor;
}
@Override
public void setShutdownOnExit(boolean shutdown) {
shutdownExecutor = shutdown;
}
public int getSendBufferSize() {
return sendBufferSize;
}
/**
* @param sendSize Size (in bytes) of buffer to use when sending files
* @see ScpHelper#MIN_SEND_BUFFER_SIZE
*/
public void setSendBufferSize(int sendSize) {
if (sendSize < ScpHelper.MIN_SEND_BUFFER_SIZE) {
throw new IllegalArgumentException("<ScpCommandFactory>() send buffer size "
+ "(" + sendSize + ") below minimum required (" + ScpHelper.MIN_SEND_BUFFER_SIZE + ")");
}
sendBufferSize = sendSize;
}
public int getReceiveBufferSize() {
return receiveBufferSize;
}
/**
* @param receiveSize Size (in bytes) of buffer to use when receiving files
* @see ScpHelper#MIN_RECEIVE_BUFFER_SIZE
*/
public void setReceiveBufferSize(int receiveSize) {
if (receiveSize < ScpHelper.MIN_RECEIVE_BUFFER_SIZE) {
throw new IllegalArgumentException("<ScpCommandFactory>() receive buffer size "
+ "(" + receiveSize + ") below minimum required (" + ScpHelper.MIN_RECEIVE_BUFFER_SIZE + ")");
}
receiveBufferSize = receiveSize;
}
/**
* @param listener The {@link ScpTransferEventListener} to add
* @return {@code true} if this is a <U>new</U> listener instance,
* {@code false} if the listener is already registered
* @throws IllegalArgumentException if {@code null} listener
*/
public boolean addEventListener(ScpTransferEventListener listener) {
if (listener == null) {
throw new IllegalArgumentException("No listener instance");
}
return listeners.add(listener);
}
/**
* @param listener The {@link ScpTransferEventListener} to remove
* @return {@code true} if the listener was registered and removed,
* {@code false} if the listener was not registered to begin with
* @throws IllegalArgumentException if {@code null} listener
*/
public boolean removeEventListener(ScpTransferEventListener listener) {
if (listener == null) {
throw new IllegalArgumentException("No listener instance");
}
return listeners.remove(listener);
}
/**
* Parses a command string and verifies that the basic syntax is
* correct. If parsing fails the responsibility is delegated to
* the configured {@link CommandFactory} instance; if one exist.
*
* @param command command to parse
* @return configured {@link Command} instance
* @throws IllegalArgumentException if not an SCP command and no
* delegate command factory is available
* @see ScpHelper#SCP_COMMAND_PREFIX
*/
@Override
public Command createCommand(String command) {
if (command.startsWith(ScpHelper.SCP_COMMAND_PREFIX)) {
return new ScpCommand(command,
getExecutorService(), isShutdownOnExit(),
getSendBufferSize(), getReceiveBufferSize(),
getScpFileOpener(), listenerProxy);
}
CommandFactory factory = getDelegateCommandFactory();
if (factory != null) {
return factory.createCommand(command);
}
throw new IllegalArgumentException("Unknown command, does not begin with '" + ScpHelper.SCP_COMMAND_PREFIX + "': " + command);
}
@Override
public ScpCommandFactory clone() {
try {
ScpCommandFactory other = getClass().cast(super.clone());
// clone the listeners set as well
other.listeners = new CopyOnWriteArraySet<>(this.listeners);
other.listenerProxy = EventListenerUtils.proxyWrapper(ScpTransferEventListener.class, getClass().getClassLoader(), other.listeners);
return other;
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e); // un-expected...
}
}
}