com.yahoo.jdisc.handler.ResponseDispatch Maven / Gradle / Ivy
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.jdisc.handler;
import com.yahoo.jdisc.Response;
import com.yahoo.jdisc.SharedResource;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* This class provides a convenient way of safely dispatching a {@link Response}. It is similar in use to {@link
* RequestDispatch}, where you need to subclass and implement and override the appropriate methods. Because a Response
* is not a {@link SharedResource}, its construction is less strenuous, and this class is able to provide a handful of
* convenient factory methods to dispatch the simplest of Responses.
* The following is a simple example on how to use this class without the factories:
*
* public void signalInternalError(ResponseHandler handler) {
* new ResponseDispatch() {
* @Override
* protected Response newResponse() {
* return new Response(Response.Status.INTERNAL_SERVER_ERROR);
* }
* @Override
* protected Iterable<ByteBuffer> responseContent() {
* return Set.of(ByteBuffer.wrap(new byte[] { 6, 9 }));
* }
* }.dispatch(handler);
* }
*
*
* @author Simon Thoresen Hult
*/
public abstract class ResponseDispatch implements Future {
private final FutureConjunction completions = new FutureConjunction();
/**
* Creates and returns the {@link Response} to dispatch.
*
* @return The Response to dispatch.
*/
protected abstract Response newResponse();
/**
* Returns an Iterable for the ByteBuffers that the {@link #dispatch(ResponseHandler)} method should write to the
* {@link Response} once it has {@link ResponseHandler#handleResponse(Response) connected}. The default
* implementation returns an empty list. Because this method uses the Iterable interface, you can provide the
* ByteBuffers lazily, or as they become available.
*
* @return The ByteBuffers to write to the Response's ContentChannel.
*/
protected Iterable responseContent() {
return List.of();
}
/**
* This methods calls {@link #newResponse()} to create a new {@link Response}, and then calls {@link
* ResponseHandler#handleResponse(Response)} with that.
*
* @param responseHandler The ResponseHandler to connect to.
* @return The ContentChannel to write the Response's content to.
*/
public final ContentChannel connect(ResponseHandler responseHandler) {
return responseHandler.handleResponse(newResponse());
}
/**
* Convenience method for constructing a {@link FastContentWriter} over the {@link ContentChannel} returned by
* calling {@link #connect(ResponseHandler)}.
*
* @param responseHandler The ResponseHandler to connect to.
* @return The FastContentWriter for the connected Response.
*/
public final FastContentWriter connectFastWriter(ResponseHandler responseHandler) {
return new FastContentWriter(connect(responseHandler));
}
/**
* This method calls {@link #connect(ResponseHandler)} to establish a {@link ContentChannel} for the {@link
* Response}, and then iterates through all the ByteBuffers returned by {@link #responseContent()} and writes them
* to that ContentChannel. This method uses a finally
block to make sure that the ContentChannel is always
* {@link ContentChannel#close(CompletionHandler) closed}.
* The returned Future will wait for all CompletionHandlers associated with the Response have been
* completed.
*
* @param responseHandler The ResponseHandler to dispatch to.
* @return A Future that can be waited for.
*/
public final CompletableFuture dispatch(ResponseHandler responseHandler) {
try (FastContentWriter writer = new FastContentWriter(connect(responseHandler))) {
for (ByteBuffer buf : responseContent()) {
writer.write(buf);
}
completions.addOperand(writer);
}
return completions.completableFuture();
}
@Override
public final boolean cancel(boolean mayInterruptIfRunning) {
throw new UnsupportedOperationException();
}
@Override
public final boolean isCancelled() {
return false;
}
@Override public boolean isDone() { return completions.isDone(); }
@Override public Boolean get() throws InterruptedException, ExecutionException { return completions.get(); }
@Override
public Boolean get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return completions.get(timeout, unit);
}
/**
* Factory method for creating a ResponseDispatch with a {@link Response} that has the given status code, and
* ByteBuffer content.
*
* @param responseStatus The status code of the Response to dispatch.
* @param content The ByteBuffer content of the Response, may be empty.
* @return The created ResponseDispatch.
*/
public static ResponseDispatch newInstance(int responseStatus, ByteBuffer... content) {
return newInstance(new Response(responseStatus), List.of(content));
}
/**
* Factory method for creating a ResponseDispatch with a {@link Response} that has the given status code, and
* collection of ByteBuffer content.
* Because this method uses the Iterable interface, you can create the ByteBuffers lazily, or
* provide them as they become available.
*
* @param responseStatus The status code of the Response to dispatch.
* @param content The provider of the Response's ByteBuffer content.
* @return The created ResponseDispatch.
*/
public static ResponseDispatch newInstance(int responseStatus, Iterable content) {
return newInstance(new Response(responseStatus), content);
}
/**
* Factory method for creating a ResponseDispatch over a given {@link Response} and ByteBuffer content.
*
* @param response The Response to dispatch.
* @param content The ByteBuffer content of the Response, may be empty.
* @return The created ResponseDispatch.
*/
public static ResponseDispatch newInstance(Response response, ByteBuffer... content) {
return newInstance(response, List.of(content));
}
/**
* Factory method for creating a ResponseDispatch over a given {@link Response} and ByteBuffer content.
* Because this method uses the Iterable interface, you can create the ByteBuffers lazily, or provide them as they
* become available.
*
* @param response The Response to dispatch.
* @param content The provider of the Response's ByteBuffer content.
* @return The created ResponseDispatch.
*/
public static ResponseDispatch newInstance(Response response, Iterable content) {
return new ResponseDispatch() {
@Override
protected Response newResponse() {
return response;
}
@Override
public Iterable responseContent() {
return content;
}
};
}
}