blob: 837224e8f7f1fb1f1cabd5678d358025949e61c3 [file] [log] [blame]
/*
* 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 org.apache.sshd.common.SshConstants;
import org.apache.sshd.common.SshException;
import org.apache.sshd.common.io.IoWriteFuture;
import org.apache.sshd.common.util.buffer.Buffer;
/**
* A {@link DefaultSshFuture} that can be used to wait for the reply of an SSH_MSG_GLOBAL_REQUEST sent with
* {@code want-reply = true}.
*
* @see {@link org.apache.sshd.common.session.Session#request(Buffer, String, ReplyHandler)}
*/
public class GlobalRequestFuture extends DefaultSshFuture<GlobalRequestFuture>
implements SshFutureListener<IoWriteFuture> {
/**
* A {@code ReplyHandler} is invoked asynchronously when the reply for a request with {@code want-reply = true} is
* received. It is <em>not</em> invoked if the request could not be sent; to catch such cases
* {@link DefaultSshFuture#await()} the {@link GlobalRequestFuture} and check
* {@link GlobalRequestFuture#getException()}.
*/
@FunctionalInterface
public interface ReplyHandler {
/**
* Invoked by the framework upon reception of the reply. If the global request was sent with
* {@code want-reply = false}, it is invoked with {@link SshConstants#SSH_MSG_REQUEST_SUCCESS} and an empty
* buffer after the request was successfully sent.
*
* @param cmd the command received, can be one of {@link SshConstants#SSH_MSG_REQUEST_SUCCESS},
* {@link SshConstants#SSH_MSG_UNIMPLEMENTED}, or {@link SshConstants#SSH_MSG_REQUEST_FAILURE}
* @param buffer the {@link Buffer} received
*/
void accept(int cmd, Buffer buffer);
}
private final ReplyHandler handler;
private long sequenceNumber;
/**
* Creates a new {@link GlobalRequestFuture} for a global request. Synchronization occurs on the future itself. The
* future will be fulfilled once the reply has been received or an error occurred.
*
* @param request the request identifier
*/
public GlobalRequestFuture(String request) {
this(request, null);
}
/**
* Creates a new {@link GlobalRequestFuture} for a global request. Synchronization occurs on the future itself. The
* future will be fulfilled once the request has been sent, or an error occurred during sending. The framework will
* invoke the handler once the reply has been received. For global requests with {@code want-reply = false}, the
* handler will be invoked with an empty buffer if the request was successfully sent.
*
* @param request the request identifier
* @param handler the {@link ReplyHandler}, or {@code null}
*/
public GlobalRequestFuture(String request, ReplyHandler handler) {
super(request, null);
this.handler = handler;
}
@Override
public String getId() {
return (String) super.getId();
}
/**
* Retrieves this future's packet sequence number.
*
* @return the sequence number
*/
public long getSequenceNumber() {
return sequenceNumber;
}
/**
* Sets the packet sequence number of the global request represented by this future.
*
* @param number the packet sequence number
* @throws IllegalArgumentException if the number given is not an unsigned 32bit value
*/
public void setSequenceNumber(long number) {
if (number < 0 || ((number & 0xFFFF_FFFFL) != number)) {
throw new IllegalArgumentException("Invalid sequence number " + number);
}
sequenceNumber = number;
}
/**
* Retrieves the {@link ReplyHandler} of this future, if any.
*
* @return the handler, or {@code null} if none was set
*/
public ReplyHandler getHandler() {
return handler;
}
/**
* Obtains the reply {@link Buffer} if the request was successful. If called after {@link #isDone()} is
* {@code true}, a non-{@code null} result means the request was successful.
*
* @return the {@link Buffer}, or {@code null} if the request was not successful or the reply was not received yet
*/
public Buffer getBuffer() {
Object value = getValue();
if (value instanceof Buffer) {
return (Buffer) value;
}
return null;
}
/**
* Retrieves an exception if the request failed. If called after {@link #isDone()} is {@code true}, a
* {@code null} result means the request did not fail.
*
* @return a failure reason, or {@code null} if there isn't one or if the request did not fail
*/
public Throwable getException() {
Object value = getValue();
if (value instanceof Throwable) {
return (Throwable) value;
}
return null;
}
@Override
public void operationComplete(IoWriteFuture future) {
if (!future.isWritten()) {
// Sending the request message failed
Throwable ioe = future.getException();
if (ioe != null) {
setValue(ioe);
} else {
setValue(new SshException("Could not write global request " + getId() + " seqNo=" + getSequenceNumber()));
}
}
}
@Override
public String toString() {
return super.toString() + "[seqNo=" + sequenceNumber + ']';
}
}