org.apache.sshd.common.future.AbstractSshFuture Maven / Gradle / Ivy
Go to download
This artifact provides a single jar that contains all classes required to use remote EJB and JMS, including
all dependencies. It is intended for use by those not using maven, maven users should just import the EJB and
JMS BOM's instead (shaded JAR's cause lots of problems with maven, as it is very easy to inadvertently end up
with different versions on classes on the class path).
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() + "]";
}
}