1 /* 2 * Copyright 2015 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc; 18 19 import com.google.common.base.Preconditions; 20 import java.util.concurrent.TimeoutException; 21 22 /** 23 * Utility methods for working with {@link Context}s in GRPC. 24 */ 25 public final class Contexts { 26 Contexts()27 private Contexts() { 28 } 29 30 /** 31 * Make the provided {@link Context} {@link Context#current()} for the creation of a listener 32 * to a received call and for all events received by that listener. 33 * 34 * <p>This utility is expected to be used by {@link ServerInterceptor} implementations that need 35 * to augment the {@link Context} in which the application does work when receiving events from 36 * the client. 37 * 38 * @param context to make {@link Context#current()}. 39 * @param call used to send responses to client. 40 * @param headers received from client. 41 * @param next handler used to create the listener to be wrapped. 42 * @return listener that will receive events in the scope of the provided context. 43 */ interceptCall( Context context, ServerCall<ReqT, RespT> call, Metadata headers, ServerCallHandler<ReqT, RespT> next)44 public static <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall( 45 Context context, 46 ServerCall<ReqT, RespT> call, 47 Metadata headers, 48 ServerCallHandler<ReqT, RespT> next) { 49 Context previous = context.attach(); 50 try { 51 return new ContextualizedServerCallListener<>( 52 next.startCall(call, headers), 53 context); 54 } finally { 55 context.detach(previous); 56 } 57 } 58 59 /** 60 * Implementation of {@link io.grpc.ForwardingServerCallListener} that attaches a context before 61 * dispatching calls to the delegate and detaches them after the call completes. 62 */ 63 private static class ContextualizedServerCallListener<ReqT> extends 64 ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT> { 65 private final Context context; 66 ContextualizedServerCallListener(ServerCall.Listener<ReqT> delegate, Context context)67 public ContextualizedServerCallListener(ServerCall.Listener<ReqT> delegate, Context context) { 68 super(delegate); 69 this.context = context; 70 } 71 72 @Override onMessage(ReqT message)73 public void onMessage(ReqT message) { 74 Context previous = context.attach(); 75 try { 76 super.onMessage(message); 77 } finally { 78 context.detach(previous); 79 } 80 } 81 82 @Override onHalfClose()83 public void onHalfClose() { 84 Context previous = context.attach(); 85 try { 86 super.onHalfClose(); 87 } finally { 88 context.detach(previous); 89 } 90 } 91 92 @Override onCancel()93 public void onCancel() { 94 Context previous = context.attach(); 95 try { 96 super.onCancel(); 97 } finally { 98 context.detach(previous); 99 } 100 } 101 102 @Override onComplete()103 public void onComplete() { 104 Context previous = context.attach(); 105 try { 106 super.onComplete(); 107 } finally { 108 context.detach(previous); 109 } 110 } 111 112 @Override onReady()113 public void onReady() { 114 Context previous = context.attach(); 115 try { 116 super.onReady(); 117 } finally { 118 context.detach(previous); 119 } 120 } 121 } 122 123 /** 124 * Returns the {@link Status} of a cancelled context or {@code null} if the context 125 * is not cancelled. 126 */ 127 @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1975") statusFromCancelled(Context context)128 public static Status statusFromCancelled(Context context) { 129 Preconditions.checkNotNull(context, "context must not be null"); 130 if (!context.isCancelled()) { 131 return null; 132 } 133 134 Throwable cancellationCause = context.cancellationCause(); 135 if (cancellationCause == null) { 136 return Status.CANCELLED.withDescription("io.grpc.Context was cancelled without error"); 137 } 138 if (cancellationCause instanceof TimeoutException) { 139 return Status.DEADLINE_EXCEEDED 140 .withDescription(cancellationCause.getMessage()) 141 .withCause(cancellationCause); 142 } 143 Status status = Status.fromThrowable(cancellationCause); 144 if (Status.Code.UNKNOWN.equals(status.getCode()) 145 && status.getCause() == cancellationCause) { 146 // If fromThrowable could not determine a status, then 147 // just return CANCELLED. 148 return Status.CANCELLED.withDescription("Context cancelled").withCause(cancellationCause); 149 } 150 return status.withCause(cancellationCause); 151 } 152 } 153