org.apache.sshd.common.future.AbstractSshFuture Maven / Gradle / Ivy
The newest version!
/*
* 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.common.future;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.StreamCorruptedException;
import java.net.ConnectException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.ExceptionUtils;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
import org.apache.sshd.common.util.threads.ThreadUtils;
/**
* @param Type of future
* @author Apache MINA SSHD Project
*/
public abstract class AbstractSshFuture> extends AbstractLoggingBean implements SshFuture {
private final Object id;
/**
* @param id Some identifier useful as {@code toString()} value
*/
protected AbstractSshFuture(Object id) {
this.id = id;
}
@Override
public Object getId() {
return id;
}
@Override
public boolean await(long timeoutMillis, CancelOption... options) throws IOException {
return await0(timeoutMillis, true, options) != null;
}
@Override
public boolean awaitUninterruptibly(long timeoutMillis, CancelOption... options) {
try {
return await0(timeoutMillis, false, options) != null;
} catch (InterruptedIOException e) {
throw formatExceptionMessage(
msg -> new IllegalStateException(msg, e),
"Unexpected interrupted exception wile awaitUninterruptibly %d msec: %s",
timeoutMillis, e.getMessage());
}
}
/**
*
* Waits (interruptible) for the specified timeout (msec.) and then checks the result:
*
*
* -
*
* If result is {@code null} then timeout is assumed to have expired - throw an appropriate {@link IOException}
*
*
*
* -
*
* If the result is of the expected type, then cast and return it
*
*
*
* -
*
* If the result is a {@link Throwable} then throw an {@link IOException} whose cause is the original exception
*
*
*
* -
*
* Otherwise (should never happen), throw a {@link StreamCorruptedException} with the name of the result type
*
*
*
*
* @param The generic result type
* @param expectedType The expected result type
* @param timeout The timeout (millis) to wait for a result
* @param options Optional {@link CancelOption}s defining the behavior on time-out or interrupt.
* @return The (never {@code null}) result
* @throws IOException If failed to retrieve the expected result on time
*/
protected R verifyResult(Class extends R> expectedType, long timeout, CancelOption... options) throws IOException {
Object value = await0(timeout, true, options);
if (value == null) {
TimeoutException cause = new TimeoutException("Timed out after " + timeout + " msec");
throw formatExceptionMessage(msg -> new SshException(msg, cause),
"Failed to get operation result within specified timeout: %s msec",
timeout);
}
if (value == GenericUtils.NULL) {
return null;
}
Class> actualType = value.getClass();
if (expectedType.isAssignableFrom(actualType)) {
return expectedType.cast(value);
}
if (value instanceof CancelFuture) {
value = ((CancelFuture) value).getBackTrace();
if (value == null) {
// Should not really occur
value = new CancellationException("Operation was cancelled before");
}
actualType = CancellationException.class;
}
if (Throwable.class.isAssignableFrom(actualType)) {
Throwable t = ExceptionUtils.peelException((Throwable) value);
if (t instanceof SshException) {
throw new SshException(((SshException) t).getDisconnectCode(), t.getMessage(), t);
}
Throwable cause = t instanceof ConnectException ? t : ExceptionUtils.resolveExceptionCause(t);
throw formatExceptionMessage(
msg -> new SshException(msg, cause),
"Failed (%s) to execute: %s",
t.getClass().getSimpleName(), t.getMessage());
} else { // what else can it be ????
throw formatExceptionMessage(
StreamCorruptedException::new, "Unknown result type: %s", actualType.getName());
}
}
/**
* Wait for the Future to be ready. If the requested delay is 0 or negative, this method returns immediately.
*
* @param timeoutMillis The delay we will wait for the Future to be ready
* @param interruptable Tells if the wait can be interrupted or not. If {@code true} and the thread is
* interrupted then an {@link InterruptedIOException} is thrown.
* @param options Optional {@link CancelOption} defining the behavior on time-out or interrupt.
* @return The non-{@code null} result object if the Future is ready, {@code null} if the
* timeout expired and no result was received
* @throws InterruptedIOException If the thread has been interrupted when it's not allowed.
*/
protected abstract Object await0(long timeoutMillis, boolean interruptable, CancelOption... options)
throws InterruptedIOException;
@SuppressWarnings("unchecked")
protected SshFutureListener asListener(Object o) {
return (SshFutureListener) o;
}
protected void notifyListener(SshFutureListener l) {
try {
T arg = asT();
ThreadUtils.runAsInternal(() -> {
l.operationComplete(arg);
return null;
});
} catch (Throwable t) {
warn("notifyListener({}) failed ({}) to invoke {}: {}",
this, t.getClass().getSimpleName(), l, t.getMessage(), t);
}
}
@SuppressWarnings("unchecked")
protected T asT() {
return (T) this;
}
/**
* Generates an exception whose message is prefixed by the future simple class name + {@link #getId() identifier} as
* a hint to the context of the failure.
*
* @param Type of {@link Throwable} being generated
* @param exceptionCreator The exception creator from the formatted message
* @param format The extra payload format as per {@link String#format(String, Object...)}
* @param args The formatting arguments
* @return The generated exception
*/
protected E formatExceptionMessage(
Function super String, ? extends E> exceptionCreator, String format, Object... args) {
String messagePayload = String.format(format, args);
String excMessage = getClass().getSimpleName() + "[" + getId() + "]: " + messagePayload;
return exceptionCreator.apply(excMessage);
}
@Override
public String toString() {
return getClass().getSimpleName() + "[id=" + getId() + "]";
}
}