/*
* 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 org.apache.sshd.common.SshException;
import org.apache.sshd.common.util.GenericUtils;
import org.apache.sshd.common.util.logging.AbstractLoggingBean;
/**
* @param <T> Type of future
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
*/
public abstract class AbstractSshFuture<T extends SshFuture> extends AbstractLoggingBean implements SshFuture<T> {
/**
* A default value to indicate the future has been canceled
*/
protected static final Object CANCELED = new Object();
protected AbstractSshFuture() {
super();
}
@Override
public boolean await(long timeoutMillis) throws IOException {
return await0(timeoutMillis, true) != null;
}
@Override
public boolean awaitUninterruptibly(long timeoutMillis) {
try {
return await0(timeoutMillis, false) != null;
} catch (InterruptedIOException e) {
throw new InternalError("Unexpected interrupted exception wile awaitUninterruptibly "
+ timeoutMillis + " msec.: " + e.getMessage(), e);
}
}
/**
* <P>Waits (interruptible) for the specified timeout (msec.) and then checks
* the result:</P>
* <UL>
* <LI><P>
* If result is {@code null} then timeout is assumed to have expired - throw
* an appropriate {@link IOException}
* </P></LI>
*
* <LI><P>
* If the result is of the expected type, then cast and return it
* </P></LI>
*
* <LI><P>
* If the result is an {@link IOException} then re-throw it
* </P></LI>
*
* <LI><P>
* If the result is a {@link Throwable} then throw an {@link IOException}
* whose cause is the original exception
* </P></LI>
*
* <LI><P>
* Otherwise (should never happen), throw a {@link StreamCorruptedException}
* with the name of the result type
* </P></LI>
* </UL>
*
* @param <R> The generic result type
* @param expectedType The expected result type
* @param timeout The timeout (millis) to wait for a result
* @return The (never {@code null}) result
* @throws IOException If failed to retrieve the expected result on time
*/
protected <R> R verifyResult(Class<? extends R> expectedType, long timeout) throws IOException {
Object value = await0(timeout, true);
if (value == null) {
throw new SshException("Failed to get operation result within specified timeout: " + timeout);
}
Class<?> actualType = value.getClass();
if (expectedType.isAssignableFrom(actualType)) {
return expectedType.cast(value);
}
if (Throwable.class.isAssignableFrom(actualType)) {
Throwable t = GenericUtils.peelException((Throwable) value);
if (t != value) {
value = t;
actualType = value.getClass();
}
if (IOException.class.isAssignableFrom(actualType)) {
throw (IOException) value;
}
throw new SshException("Failed (" + t.getClass().getSimpleName() + ") to execute: " + t.getMessage(), GenericUtils.resolveExceptionCause(t));
} else { // what else can it be ????
throw new StreamCorruptedException("Unknown result type: " + 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.
* @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) throws InterruptedIOException;
@SuppressWarnings("unchecked")
protected SshFutureListener<T> asListener(Object o) {
return (SshFutureListener<T>) o;
}
protected void notifyListener(SshFutureListener<T> l) {
try {
l.operationComplete(asT());
} catch (Throwable t) {
log.warn("notifyListener({}) failed ({}) to invoke {}: {}",
this, t.getClass().getSimpleName(), l, t.getMessage());
if (log.isDebugEnabled()) {
log.debug("notifyListener(" + this + ")[" + l + "] invocation failure details", t);
}
}
}
@SuppressWarnings("unchecked")
protected T asT() {
return (T) this;
}
}