com.linecorp.armeria.common.RequestContext.java Source code

Java tutorial

Introduction

Here is the source code for com.linecorp.armeria.common.RequestContext.java

Source

/*
 * Copyright 2016 LINE Corporation
 *
 * LINE Corporation 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 com.linecorp.armeria.common;

import java.util.Iterator;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.function.Supplier;

import javax.annotation.Nullable;

import org.slf4j.LoggerFactory;

import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.common.logging.RequestLogBuilder;
import com.linecorp.armeria.common.logging.ResponseLog;
import com.linecorp.armeria.common.logging.ResponseLogBuilder;
import com.linecorp.armeria.common.util.Exceptions;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.util.Attribute;
import io.netty.util.AttributeMap;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.ReferenceCounted;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GenericFutureListener;
import io.netty.util.concurrent.Promise;

/**
 * Provides information about an invocation and related utilities. Every remote invocation, regardless of if
 * it's client side or server side, has its own {@link RequestContext} instance.
 */
public interface RequestContext extends AttributeMap {

    /**
     * Returns the context of the invocation that is being handled in the current thread.
     *
     * @throws IllegalStateException if the context is unavailable in the current thread
     */
    static <T extends RequestContext> T current() {
        final T ctx = RequestContextThreadLocal.get();
        if (ctx == null) {
            throw new IllegalStateException(RequestContext.class.getSimpleName() + " unavailable");
        }
        return ctx;
    }

    /**
     * Maps the context of the invocation that is being handled in the current thread.
     *
     * @param mapper the {@link Function} that maps the invocation
     * @param defaultValueSupplier the {@link Supplier} that provides the value when the context is unavailable
     *                             in the current thread. If {@code null}, the {@code null} will be returned
     *                             when the context is unavailable in the current thread.
     */
    static <T> T mapCurrent(Function<? super RequestContext, T> mapper,
            @Nullable Supplier<T> defaultValueSupplier) {

        final RequestContext ctx = RequestContextThreadLocal.get();
        if (ctx != null) {
            return mapper.apply(ctx);
        }

        if (defaultValueSupplier != null) {
            return defaultValueSupplier.get();
        }

        return null;
    }

    /**
     * Pushes the specified context to the thread-local stack. To pop the context from the stack, call
     * {@link PushHandle#close()}, which can be done using a {@code try-finally} block:
     * <pre>{@code
     * try (PushHandler ignored = RequestContext.push(ctx)) {
     *     ...
     * }
     * }</pre>
     *
     * <p>The callbacks added by {@link #onEnter(Runnable)} and {@link #onExit(Runnable)} will be invoked
     * when the context is pushed to and removed from the thread-local stack respectively.
     *
     * <p>NOTE: In case of re-entrance, the callbacks will never run.
     */
    static PushHandle push(RequestContext ctx) {
        return push(ctx, true);
    }

    /**
     * Pushes the specified context to the thread-local stack. To pop the context from the stack, call
     * {@link PushHandle#close()}, which can be done using a {@code try-finally} block:
     * <pre>{@code
     * try (PushHandler ignored = RequestContext.push(ctx, true)) {
     *     ...
     * }
     * }</pre>
     *
     * <p>NOTE: This method is only useful when it is undesirable to invoke the callbacks, such as replacing
     *          the current context with another. Prefer {@link #push(RequestContext)} otherwise.
     *
     * @param runCallbacks if {@code true}, the callbacks added by {@link #onEnter(Runnable)} and
     *                     {@link #onExit(Runnable)} will be invoked when the context is pushed to and
     *                     removed from the thread-local stack respectively.
     *                     If {@code false}, no callbacks will be executed.
     *                     NOTE: In case of re-entrance, the callbacks will never run.
     */
    static PushHandle push(RequestContext ctx, boolean runCallbacks) {
        final RequestContext oldCtx = RequestContextThreadLocal.getAndSet(ctx);
        if (oldCtx == ctx) {
            // Reentrance
            return () -> {
            };
        }

        if (runCallbacks) {
            ctx.invokeOnEnterCallbacks();
            if (oldCtx != null) {
                return () -> {
                    ctx.invokeOnExitCallbacks();
                    RequestContextThreadLocal.set(oldCtx);
                };
            } else {
                return () -> {
                    ctx.invokeOnExitCallbacks();
                    RequestContextThreadLocal.remove();
                };
            }
        } else {
            if (oldCtx != null) {
                return () -> RequestContextThreadLocal.set(oldCtx);
            } else {
                return RequestContextThreadLocal::remove;
            }
        }
    }

    /**
     * Returns the {@link SessionProtocol} of this request.
     */
    SessionProtocol sessionProtocol();

    String method();

    /**
     * Returns the absolute path part of this invocation, as defined in
     * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2">the
     * section 5.1.2 of RFC2616</a>.
     */
    String path();

    /**
     * Returns the request associated with this context.
     */
    <T> T request();

    RequestLogBuilder requestLogBuilder();

    ResponseLogBuilder responseLogBuilder();

    CompletableFuture<RequestLog> requestLogFuture();

    CompletableFuture<ResponseLog> responseLogFuture();

    Iterator<Attribute<?>> attrs();

    /**
     * Returns the {@link EventLoop} that is handling this invocation.
     */
    EventLoop eventLoop();

    /**
     * Returns an {@link EventLoop} that will make sure this invocation is set
     * as the current invocation before executing any callback. This should
     * almost always be used for executing asynchronous callbacks in service
     * code to make sure features that require the invocation context work
     * properly. Most asynchronous libraries like
     * {@link CompletableFuture} provide methods that
     * accept an {@link Executor} to run callbacks on.
     */
    default EventLoop contextAwareEventLoop() {
        return new RequestContextAwareEventLoop(this, eventLoop());
    }

    /**
     * Returns an {@link Executor} that will execute callbacks in the given
     * {@code executor}, making sure to propagate the current invocation context
     * into the callback execution. It is generally preferred to use
     * {@link #contextAwareEventLoop()} to ensure the callback stays on the
     * same thread as well.
     */
    default Executor makeContextAware(Executor executor) {
        return runnable -> executor.execute(makeContextAware(runnable));
    }

    /**
     * Returns a {@link Callable} that makes sure the current invocation context
     * is set and then invokes the input {@code callable}.
     */
    <T> Callable<T> makeContextAware(Callable<T> callable);

    /**
     * Returns a {@link Runnable} that makes sure the current invocation context
     * is set and then invokes the input {@code runnable}.
     */
    Runnable makeContextAware(Runnable runnable);

    /**
     * Returns a {@link FutureListener} that makes sure the current invocation
     * context is set and then invokes the input {@code listener}.
     */
    @Deprecated
    <T> FutureListener<T> makeContextAware(FutureListener<T> listener);

    /**
     * Returns a {@link ChannelFutureListener} that makes sure the current invocation
     * context is set and then invokes the input {@code listener}.
     */
    @Deprecated
    ChannelFutureListener makeContextAware(ChannelFutureListener listener);

    /**
     * Returns a {@link GenericFutureListener} that makes sure the current invocation
     * context is set and then invokes the input {@code listener}.
     */
    @Deprecated
    <T extends Future<?>> GenericFutureListener<T> makeContextAware(GenericFutureListener<T> listener);

    /**
     * Registers {@code callback} to be run when re-entering this {@link RequestContext},
     * usually when using the {@link #makeContextAware} family of methods. Any thread-local state
     * associated with this context should be restored by this callback.
     */
    void onEnter(Runnable callback);

    /**
     * Registers {@code callback} to be run when re-exiting this {@link RequestContext},
     * usually when using the {@link #makeContextAware} family of methods. Any thread-local state
     * associated with this context should be reset by this callback.
     */
    void onExit(Runnable callback);

    /**
     * Invokes all {@link #onEnter(Runnable)} callbacks. It is discouraged to use this method directly.
     * Use {@link #makeContextAware(Runnable)} or {@link #push(RequestContext, boolean)} instead so that
     * the callbacks are invoked automatically.
     */
    void invokeOnEnterCallbacks();

    /**
     * Invokes all {@link #onExit(Runnable)} callbacks. It is discouraged to use this method directly.
     * Use {@link #makeContextAware(Runnable)} or {@link #push(RequestContext, boolean)} instead so that
     * the callbacks are invoked automatically.
     */
    void invokeOnExitCallbacks();

    /**
     * Resolves the specified {@code promise} with the specified {@code result} so that the {@code promise} is
     * marked as 'done'. If {@code promise} is done already, this method does the following:
     * <ul>
     *   <li>Log a warning about the failure, and</li>
     *   <li>Release {@code result} if it is {@linkplain ReferenceCounted a reference-counted object},
     *       such as {@link ByteBuf} and {@link FullHttpResponse}.</li>
     * </ul>
     * Note that a {@link Promise} can be done already even if you did not call this method in the following
     * cases:
     * <ul>
     *   <li>Invocation timeout - The invocation associated with the {@link Promise} has been timed out.</li>
     *   <li>User error - A service implementation called any of the following methods more than once:
     *     <ul>
     *       <li>{@link #resolvePromise(Promise, Object)}</li>
     *       <li>{@link #rejectPromise(Promise, Throwable)}</li>
     *       <li>{@link Promise#setSuccess(Object)}</li>
     *       <li>{@link Promise#setFailure(Throwable)}</li>
     *       <li>{@link Promise#cancel(boolean)}</li>
     *     </ul>
     *   </li>
     * </ul>
     */
    @Deprecated
    default void resolvePromise(Promise<?> promise, Object result) {
        @SuppressWarnings("unchecked")
        final Promise<Object> castPromise = (Promise<Object>) promise;

        if (castPromise.trySuccess(result)) {
            // Resolved successfully.
            return;
        }

        try {
            if (!(promise.cause() instanceof TimeoutException)) {
                // Log resolve failure unless it is due to a timeout.
                LoggerFactory.getLogger(RequestContext.class)
                        .warn("Failed to resolve a completed promise ({}) with {}", promise, result);
            }
        } finally {
            ReferenceCountUtil.safeRelease(result);
        }
    }

    /**
     * Rejects the specified {@code promise} with the specified {@code cause}. If {@code promise} is done
     * already, this method logs a warning about the failure. Note that a {@link Promise} can be done already
     * even if you did not call this method in the following cases:
     * <ul>
     *   <li>Invocation timeout - The invocation associated with the {@link Promise} has been timed out.</li>
     *   <li>User error - A service implementation called any of the following methods more than once:
     *     <ul>
     *       <li>{@link #resolvePromise(Promise, Object)}</li>
     *       <li>{@link #rejectPromise(Promise, Throwable)}</li>
     *       <li>{@link Promise#setSuccess(Object)}</li>
     *       <li>{@link Promise#setFailure(Throwable)}</li>
     *       <li>{@link Promise#cancel(boolean)}</li>
     *     </ul>
     *   </li>
     * </ul>
     */
    @Deprecated
    default void rejectPromise(Promise<?> promise, Throwable cause) {
        if (promise.tryFailure(cause)) {
            // Fulfilled successfully.
            return;
        }

        final Throwable firstCause = promise.cause();
        if (firstCause instanceof TimeoutException) {
            // Timed out already.
            return;
        }

        if (Exceptions.isExpected(cause)) {
            // The exception that was thrown after firstCause (often a transport-layer exception)
            // was a usual expected exception, not an error.
            return;
        }

        LoggerFactory.getLogger(RequestContext.class).warn("Failed to reject a completed promise ({}) with {}",
                promise, cause, cause);
    }

    @FunctionalInterface
    interface PushHandle extends AutoCloseable {
        @Override
        void close();
    }
}