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 static com.google.common.base.Preconditions.checkArgument;
20 
21 import com.google.common.base.MoreObjects;
22 import com.google.common.base.Preconditions;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.List;
27 import java.util.concurrent.Executor;
28 import java.util.concurrent.TimeUnit;
29 import javax.annotation.CheckReturnValue;
30 import javax.annotation.Nullable;
31 import javax.annotation.concurrent.Immutable;
32 
33 /**
34  * The collection of runtime options for a new RPC call.
35  *
36  * <p>A field that is not set is {@code null}.
37  */
38 @Immutable
39 @CheckReturnValue
40 public final class CallOptions {
41   /**
42    * A blank {@code CallOptions} that all fields are not set.
43    */
44   public static final CallOptions DEFAULT = new CallOptions();
45 
46   // Although {@code CallOptions} is immutable, its fields are not final, so that we can initialize
47   // them outside of constructor. Otherwise the constructor will have a potentially long list of
48   // unnamed arguments, which is undesirable.
49   private Deadline deadline;
50   private Executor executor;
51 
52   @Nullable
53   private String authority;
54 
55   @Nullable
56   private CallCredentials credentials;
57 
58   @Nullable
59   private String compressorName;
60 
61   private Object[][] customOptions = new Object[0][2];
62 
63   // Unmodifiable list
64   private List<ClientStreamTracer.Factory> streamTracerFactories = Collections.emptyList();
65 
66   /**
67    * Opposite to fail fast.
68    */
69   private boolean waitForReady;
70 
71   @Nullable
72   private Integer maxInboundMessageSize;
73   @Nullable
74   private Integer maxOutboundMessageSize;
75 
76 
77   /**
78    * Override the HTTP/2 authority the channel claims to be connecting to. <em>This is not
79    * generally safe.</em> Overriding allows advanced users to re-use a single Channel for multiple
80    * services, even if those services are hosted on different domain names. That assumes the
81    * server is virtually hosting multiple domains and is guaranteed to continue doing so. It is
82    * rare for a service provider to make such a guarantee. <em>At this time, there is no security
83    * verification of the overridden value, such as making sure the authority matches the server's
84    * TLS certificate.</em>
85    */
86   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767")
withAuthority(@ullable String authority)87   public CallOptions withAuthority(@Nullable String authority) {
88     CallOptions newOptions = new CallOptions(this);
89     newOptions.authority = authority;
90     return newOptions;
91   }
92 
93   /**
94    * Returns a new {@code CallOptions} with the given call credentials.
95    */
withCallCredentials(@ullable CallCredentials credentials)96   public CallOptions withCallCredentials(@Nullable CallCredentials credentials) {
97     CallOptions newOptions = new CallOptions(this);
98     newOptions.credentials = credentials;
99     return newOptions;
100   }
101 
102   /**
103    * Sets the compression to use for the call.  The compressor must be a valid name known in the
104    * {@link CompressorRegistry}.
105    */
106   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
withCompression(@ullable String compressorName)107   public CallOptions withCompression(@Nullable String compressorName) {
108     CallOptions newOptions = new CallOptions(this);
109     newOptions.compressorName = compressorName;
110     return newOptions;
111   }
112 
113   /**
114    * Returns a new {@code CallOptions} with the given absolute deadline.
115    *
116    * <p>This is mostly used for propagating an existing deadline. {@link #withDeadlineAfter} is the
117    * recommended way of setting a new deadline,
118    *
119    * @param deadline the deadline or {@code null} for unsetting the deadline.
120    */
withDeadline(@ullable Deadline deadline)121   public CallOptions withDeadline(@Nullable Deadline deadline) {
122     CallOptions newOptions = new CallOptions(this);
123     newOptions.deadline = deadline;
124     return newOptions;
125   }
126 
127   /**
128    * Returns a new {@code CallOptions} with a deadline that is after the given {@code duration} from
129    * now.
130    */
withDeadlineAfter(long duration, TimeUnit unit)131   public CallOptions withDeadlineAfter(long duration, TimeUnit unit) {
132     return withDeadline(Deadline.after(duration, unit));
133   }
134 
135   /**
136    * Returns the deadline or {@code null} if the deadline is not set.
137    */
138   @Nullable
getDeadline()139   public Deadline getDeadline() {
140     return deadline;
141   }
142 
143   /**
144    * Enables <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">
145    * 'wait for ready'</a> feature for the call. 'Fail fast' is the default option for gRPC calls
146    * and 'wait for ready' is the opposite to it.
147    */
withWaitForReady()148   public CallOptions withWaitForReady() {
149     CallOptions newOptions = new CallOptions(this);
150     newOptions.waitForReady = true;
151     return newOptions;
152   }
153 
154   /**
155    * Disables 'wait for ready' feature for the call.
156    * This method should be rarely used because the default is without 'wait for ready'.
157    */
withoutWaitForReady()158   public CallOptions withoutWaitForReady() {
159     CallOptions newOptions = new CallOptions(this);
160     newOptions.waitForReady = false;
161     return newOptions;
162   }
163 
164   /**
165    * Returns the compressor's name.
166    */
167   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1704")
168   @Nullable
getCompressor()169   public String getCompressor() {
170     return compressorName;
171   }
172 
173   /**
174    * Override the HTTP/2 authority the channel claims to be connecting to. <em>This is not
175    * generally safe.</em> Overriding allows advanced users to re-use a single Channel for multiple
176    * services, even if those services are hosted on different domain names. That assumes the
177    * server is virtually hosting multiple domains and is guaranteed to continue doing so. It is
178    * rare for a service provider to make such a guarantee. <em>At this time, there is no security
179    * verification of the overridden value, such as making sure the authority matches the server's
180    * TLS certificate.</em>
181    */
182   @Nullable
183   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1767")
getAuthority()184   public String getAuthority() {
185     return authority;
186   }
187 
188   /**
189    * Returns the call credentials.
190    */
191   @Nullable
getCredentials()192   public CallCredentials getCredentials() {
193     return credentials;
194   }
195 
196   /**
197    * Returns a new {@code CallOptions} with {@code executor} to be used instead of the default
198    * executor specified with {@link ManagedChannelBuilder#executor}.
199    */
withExecutor(Executor executor)200   public CallOptions withExecutor(Executor executor) {
201     CallOptions newOptions = new CallOptions(this);
202     newOptions.executor = executor;
203     return newOptions;
204   }
205 
206   /**
207    * Returns a new {@code CallOptions} with a {@code ClientStreamTracerFactory} in addition to
208    * the existing factories.
209    *
210    * <p>This method doesn't replace existing factories, or try to de-duplicate factories.
211    */
212   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
withStreamTracerFactory(ClientStreamTracer.Factory factory)213   public CallOptions withStreamTracerFactory(ClientStreamTracer.Factory factory) {
214     CallOptions newOptions = new CallOptions(this);
215     ArrayList<ClientStreamTracer.Factory> newList =
216         new ArrayList<ClientStreamTracer.Factory>(streamTracerFactories.size() + 1);
217     newList.addAll(streamTracerFactories);
218     newList.add(factory);
219     newOptions.streamTracerFactories = Collections.unmodifiableList(newList);
220     return newOptions;
221   }
222 
223   /**
224    * Returns an immutable list of {@code ClientStreamTracerFactory}s.
225    */
226   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
getStreamTracerFactories()227   public List<ClientStreamTracer.Factory> getStreamTracerFactories() {
228     return streamTracerFactories;
229   }
230 
231   /**
232    * Key for a key-value pair. Uses reference equality.
233    */
234   public static final class Key<T> {
235     private final String debugString;
236     private final T defaultValue;
237 
Key(String debugString, T defaultValue)238     private Key(String debugString, T defaultValue) {
239       this.debugString = debugString;
240       this.defaultValue = defaultValue;
241     }
242 
243     /**
244      * Returns the user supplied default value for this key.
245      */
getDefault()246     public T getDefault() {
247       return defaultValue;
248     }
249 
250     @Override
toString()251     public String toString() {
252       return debugString;
253     }
254 
255     /**
256      * Factory method for creating instances of {@link Key}.
257      *
258      * @param debugString a string used to describe this key, used for debugging.
259      * @param defaultValue default value to return when value for key not set
260      * @param <T> Key type
261      * @return Key object
262      * @deprecated Use {@link #create} or {@link #createWithDefault} instead. This method will
263      *     be removed.
264      */
265     @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1869")
266     @Deprecated
of(String debugString, T defaultValue)267     public static <T> Key<T> of(String debugString, T defaultValue) {
268       Preconditions.checkNotNull(debugString, "debugString");
269       return new Key<T>(debugString, defaultValue);
270     }
271 
272     /**
273      * Factory method for creating instances of {@link Key}. The default value of the
274      * key is {@code null}.
275      *
276      * @param debugString a debug string that describes this key.
277      * @param <T> Key type
278      * @return Key object
279      * @since 1.13.0
280      */
create(String debugString)281     public static <T> Key<T> create(String debugString) {
282       Preconditions.checkNotNull(debugString, "debugString");
283       return new Key<T>(debugString, /*defaultValue=*/ null);
284     }
285 
286     /**
287      * Factory method for creating instances of {@link Key}.
288      *
289      * @param debugString a debug string that describes this key.
290      * @param defaultValue default value to return when value for key not set
291      * @param <T> Key type
292      * @return Key object
293      * @since 1.13.0
294      */
createWithDefault(String debugString, T defaultValue)295     public static <T> Key<T> createWithDefault(String debugString, T defaultValue) {
296       Preconditions.checkNotNull(debugString, "debugString");
297       return new Key<T>(debugString, defaultValue);
298     }
299   }
300 
301   /**
302    * Sets a custom option. Any existing value for the key is overwritten.
303    *
304    * @param key The option key
305    * @param value The option value.
306    * @since 1.13.0
307    */
withOption(Key<T> key, T value)308   public <T> CallOptions withOption(Key<T> key, T value) {
309     Preconditions.checkNotNull(key, "key");
310     Preconditions.checkNotNull(value, "value");
311 
312     CallOptions newOptions = new CallOptions(this);
313     int existingIdx = -1;
314     for (int i = 0; i < customOptions.length; i++) {
315       if (key.equals(customOptions[i][0])) {
316         existingIdx = i;
317         break;
318       }
319     }
320 
321     newOptions.customOptions = new Object[customOptions.length + (existingIdx == -1 ? 1 : 0)][2];
322     System.arraycopy(customOptions, 0, newOptions.customOptions, 0, customOptions.length);
323 
324     if (existingIdx == -1) {
325       // Add a new option
326       newOptions.customOptions[customOptions.length] = new Object[] {key, value};
327     } else {
328       // Replace an existing option
329       newOptions.customOptions[existingIdx][1] = value;
330     }
331 
332     return newOptions;
333   }
334 
335   /**
336    * Get the value for a custom option or its inherent default.
337    * @param key Key identifying option
338    */
339   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/1869")
340   @SuppressWarnings("unchecked")
getOption(Key<T> key)341   public <T> T getOption(Key<T> key) {
342     Preconditions.checkNotNull(key, "key");
343     for (int i = 0; i < customOptions.length; i++) {
344       if (key.equals(customOptions[i][0])) {
345         return (T) customOptions[i][1];
346       }
347     }
348     return key.defaultValue;
349   }
350 
351   @Nullable
getExecutor()352   public Executor getExecutor() {
353     return executor;
354   }
355 
CallOptions()356   private CallOptions() {
357   }
358 
359   /**
360    * Returns whether <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">
361    * 'wait for ready'</a> option is enabled for the call. 'Fail fast' is the default option for gRPC
362    * calls and 'wait for ready' is the opposite to it.
363    */
isWaitForReady()364   public boolean isWaitForReady() {
365     return waitForReady;
366   }
367 
368   /**
369    * Sets the maximum allowed message size acceptable from the remote peer.  If unset, this will
370    * default to the value set on the {@link ManagedChannelBuilder#maxInboundMessageSize(int)}.
371    */
372   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
withMaxInboundMessageSize(int maxSize)373   public CallOptions withMaxInboundMessageSize(int maxSize) {
374     checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize);
375     CallOptions newOptions = new CallOptions(this);
376     newOptions.maxInboundMessageSize = maxSize;
377     return newOptions;
378   }
379 
380   /**
381    * Sets the maximum allowed message size acceptable sent to the remote peer.
382    */
383   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
withMaxOutboundMessageSize(int maxSize)384   public CallOptions withMaxOutboundMessageSize(int maxSize) {
385     checkArgument(maxSize >= 0, "invalid maxsize %s", maxSize);
386     CallOptions newOptions = new CallOptions(this);
387     newOptions.maxOutboundMessageSize = maxSize;
388     return newOptions;
389   }
390 
391   /**
392    * Gets the maximum allowed message size acceptable from the remote peer.
393    */
394   @Nullable
395   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
getMaxInboundMessageSize()396   public Integer getMaxInboundMessageSize() {
397     return maxInboundMessageSize;
398   }
399 
400   /**
401    * Gets the maximum allowed message size acceptable to send the remote peer.
402    */
403   @Nullable
404   @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2563")
getMaxOutboundMessageSize()405   public Integer getMaxOutboundMessageSize() {
406     return maxOutboundMessageSize;
407   }
408 
409   /**
410    * Copy constructor.
411    */
CallOptions(CallOptions other)412   private CallOptions(CallOptions other) {
413     deadline = other.deadline;
414     authority = other.authority;
415     credentials = other.credentials;
416     executor = other.executor;
417     compressorName = other.compressorName;
418     customOptions = other.customOptions;
419     waitForReady = other.waitForReady;
420     maxInboundMessageSize = other.maxInboundMessageSize;
421     maxOutboundMessageSize = other.maxOutboundMessageSize;
422     streamTracerFactories = other.streamTracerFactories;
423   }
424 
425   @Override
toString()426   public String toString() {
427     return MoreObjects.toStringHelper(this)
428         .add("deadline", deadline)
429         .add("authority", authority)
430         .add("callCredentials", credentials)
431         .add("executor", executor != null ? executor.getClass() : null)
432         .add("compressorName", compressorName)
433         .add("customOptions", Arrays.deepToString(customOptions))
434         .add("waitForReady", isWaitForReady())
435         .add("maxInboundMessageSize", maxInboundMessageSize)
436         .add("maxOutboundMessageSize", maxOutboundMessageSize)
437         .add("streamTracerFactories", streamTracerFactories)
438         .toString();
439   }
440 }
441