Skip to content

Instantly share code, notes, and snippets.

@nsivabalan
Created August 21, 2020 15:38
Show Gist options
  • Save nsivabalan/882ea3bfa92477fb257fcca1c7d3e609 to your computer and use it in GitHub Desktop.
Save nsivabalan/882ea3bfa92477fb257fcca1c7d3e609 to your computer and use it in GitHub Desktop.
/**
* Class that holds config parameters for a {@link UberClientCall}. The collection of runtime
* options for a new RPC call. This will be used by application and feature teams to set config
* params like like timeouts, retry policy, etc for the network calls. This mimics {@link
* CallOptions} in the grpc world.
*/
public class UberCallOptions {
/** A blank {@code UberCallOptions} that all fields are not set. */
public static final UberCallOptions DEFAULT = new UberCallOptions();
// configs from CallOptions
@Nullable private Deadline deadline;
@Nullable private Executor executor;
@Nullable private Boolean waitForReady;
private Object[][] customOptions = new Object[0][2];
// extra config options to be exposed to end user
@Nullable private String userDefinedHost;
@Nullable private Integer userDefinedPort;
// Unmodifiable list
private List<ClientStreamTracer.Factory> streamTracerFactories = Collections.emptyList();
public UberCallOptions() {}
/** Copy constructor. */
public UberCallOptions(UberCallOptions other) {
deadline = other.deadline;
executor = other.executor;
customOptions = other.customOptions;
userDefinedHost = other.userDefinedHost;
userDefinedPort = other.userDefinedPort;
waitForReady = other.waitForReady;
streamTracerFactories = other.streamTracerFactories;
}
/** @return the {@link Deadline} associated to this call. */
@Nullable
public Deadline getDeadline() {
return deadline;
}
/** @return the {@link Executor} to be used for this call. */
@Nullable
public Executor getExecutor() {
return executor;
}
/** @return the custom options as part of this call. */
public Object[][] getCustomOptions() {
return customOptions;
}
/** @return the list of streamtracer factories as part of this call. */
public List<ClientStreamTracer.Factory> getStreamTracerFactories() {
return streamTracerFactories;
}
/** @return the user defined host. */
@Nullable
public String getUserDefinedHost() {
return userDefinedHost;
}
/** @return the user defined port. */
@Nullable
public Integer getUserDefinedPort() {
return userDefinedPort;
}
/**
* Returns whether <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">'wait
* for ready'</a> option is enabled for the call. 'Fail fast' is the default option for gRPC calls
* and 'wait for ready' is the opposite to it.
*
* @return true if wait for ready is set. else false.
*/
public boolean isWaitForReady() {
return waitForReady != null ? Boolean.TRUE.equals(waitForReady) : false;
}
/** @return the value for {@link #waitForReady}. */
@Nullable
Boolean getWaitForReady() {
return waitForReady;
}
/**
* Returns a new {@code UberCallOptions} with {@code executor} to be used instead of the default
* executor.
*
* @param executor instance of executor to be set.
* @return the new {@link UberCallOptions} with updated {@link Executor}.
*/
public UberCallOptions withExecutor(@Nullable Executor executor) {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.executor = executor;
return newOptions;
}
/**
* Returns a new {@code UberCallOptions} with the given absolute deadline.
*
* <p>This is mostly used for propagating an existing deadline. {@link #withDeadlineAfter} is the
* recommended way of setting a new deadline,
*
* @param deadline the deadline or {@code null} for unsetting the deadline.
* @return the new {@link UberCallOptions} with updated deadline.
*/
public UberCallOptions withDeadline(@Nullable Deadline deadline) {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.deadline = deadline;
return newOptions;
}
/**
* Returns a new {@code CallOptions} with a deadline that is after the given {@code duration} from
* now.
*
* @param duration the duration after which call should be expired.
* @param unit unit of the duration.
* @return the new {@link UberCallOptions} with updated deadline.
*/
public UberCallOptions withDeadlineAfter(long duration, TimeUnit unit) {
return withDeadline(Deadline.after(duration, unit));
}
/**
* Enables <a href="https://github.com/grpc/grpc/blob/master/doc/wait-for-ready.md">'wait for
* ready'</a> feature for the call. 'Fail fast' is the default option for gRPC calls and 'wait for
* ready' is the opposite to it.
*
* @return the new {@link UberCallOptions} with updated waitForReady.
*/
public UberCallOptions withWaitForReady() {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.waitForReady = Boolean.TRUE;
return newOptions;
}
/**
* Disables 'wait for ready' feature for the call. This method should be rarely used because the
* default is without 'wait for ready'.
*
* @return the new {@link UberCallOptions} with updated waitForReady.
*/
public UberCallOptions withoutWaitForReady() {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.waitForReady = Boolean.FALSE;
return newOptions;
}
/**
* Returns a new {@code UberCallOptions} with a {@code ClientStreamTracerFactory} in addition to
* the existing factories.
*
* @param factory streamtracer factory to be added.
* @return the new {@link UberCallOptions} with added streamtracer factory.
* <p>This method doesn't replace existing factories, or try to de-duplicate factories.
*/
public UberCallOptions withStreamTracerFactory(ClientStreamTracer.Factory factory) {
UberCallOptions newOptions = new UberCallOptions(this);
ArrayList<ClientStreamTracer.Factory> newList =
new ArrayList<>(streamTracerFactories.size() + 1);
newList.addAll(streamTracerFactories);
newList.add(factory);
newOptions.streamTracerFactories = Collections.unmodifiableList(newList);
return newOptions;
}
/**
* Updates user defined host for the call.
*
* @param userDefinedHost user defined host.
* @return the new {@link UberCallOptions} with updated userdefined host.
*/
public UberCallOptions withUserDefinedHost(String userDefinedHost) {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.userDefinedHost = userDefinedHost;
return newOptions;
}
/**
* Updates user defined port for the call.
*
* @param userDefinedPort user defined port.
* @return the new {@link UberCallOptions} with updated userdefined port.
*/
public UberCallOptions withUserDefinedPort(int userDefinedPort) {
UberCallOptions newOptions = new UberCallOptions(this);
newOptions.userDefinedPort = userDefinedPort;
return newOptions;
}
/**
* Ignores StreamtracerFactories for now.
*
* @param o instance of {@link UberCallOptions} object to be compared for equality.
* @return the result for comparison.
*/
@Override
@SuppressWarnings("NullAway")
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof UberCallOptions)) {
return false;
}
UberCallOptions that = (UberCallOptions) o;
return ((getUserDefinedPort() == null && that.getUserDefinedPort() == null)
|| getUserDefinedPort().equals(that.getUserDefinedPort()))
&& ((getDeadline() == null && that.getDeadline() == null)
|| getDeadline().equals(that.getDeadline()))
&& ((getExecutor() == null && that.getExecutor() == null)
|| getExecutor().equals(getExecutor()))
&& isWaitForReady() == that.isWaitForReady()
&& Arrays.equals(getCustomOptions(), that.getCustomOptions())
&& ((getUserDefinedHost() == null && that.getUserDefinedHost() == null)
|| getUserDefinedHost().equals(that.getUserDefinedHost()));
}
@Override
public int hashCode() {
Object[] objects = {
getDeadline(), getExecutor(), isWaitForReady(), getUserDefinedHost(), getUserDefinedPort()
};
int result = Arrays.hashCode(objects);
result = 31 * result + Arrays.hashCode(getCustomOptions());
return result;
}
/**
* Sets a custom option. Any existing value for the key is overwritten.
*
* @param key The option key
* @param value The option value.
* @param <T> generic type of custom key.
* @return the new {@link UberCallOptions} with added custom entry.
*/
public <T> UberCallOptions withOption(UberCallOptions.Key<T> key, T value) {
Preconditions.checkNotNull(key, "key");
Preconditions.checkNotNull(value, "value");
UberCallOptions newOptions = new UberCallOptions(this);
int existingIdx = -1;
for (int i = 0; i < customOptions.length; i++) {
if (key.equals(customOptions[i][0])) {
existingIdx = i;
break;
}
}
newOptions.customOptions = new Object[customOptions.length + (existingIdx == -1 ? 1 : 0)][2];
System.arraycopy(customOptions, 0, newOptions.customOptions, 0, customOptions.length);
if (existingIdx == -1) {
// Add a new option
newOptions.customOptions[customOptions.length] = new Object[] {key, value};
} else {
// Replace an existing option
newOptions.customOptions[existingIdx] = new Object[] {key, value};
}
return newOptions;
}
/**
* Get the value for a custom option or its inherent default.
*
* @param key Key identifying option
* @param <T> generic type of custom key.
* @return the value pertaining to the custom key.
*/
@Nullable
public <T> T getOption(UberCallOptions.Key<T> key) {
Preconditions.checkNotNull(key, "key");
for (int i = 0; i < customOptions.length; i++) {
if (key.equals(customOptions[i][0])) {
return (T) customOptions[i][1];
}
}
return key.defaultValue;
}
/**
* Key for a key-value pair. Uses reference equality.
*
* @param <T> generic type of custom key.
*/
public static final class Key<T> {
private final String debugString;
@Nullable private final T defaultValue;
private Key(String debugString, @Nullable T defaultValue) {
this.debugString = debugString;
this.defaultValue = defaultValue;
}
/** @return the user supplied default value for this key. */
@Nullable
public T getDefault() {
return defaultValue;
}
@Override
public String toString() {
return debugString;
}
/**
* Factory method for creating instances of {@link UberCallOptions.Key}. The default value of
* the key is {@code null}.
*
* @param debugString a debug string that describes this key.
* @param <T> Key type
* @return Key object
*/
public static <T> UberCallOptions.Key<T> create(String debugString) {
Preconditions.checkNotNull(debugString, "debugString");
return new UberCallOptions.Key<>(debugString, /*defaultValue=*/ null);
}
/**
* Factory method for creating instances of {@link UberCallOptions.Key}.
*
* @param debugString a debug string that describes this key.
* @param defaultValue default value to return when value for key not set
* @param <T> Key type
* @return Key object
*/
public static <T> UberCallOptions.Key<T> createWithDefault(
String debugString, @Nullable T defaultValue) {
Preconditions.checkNotNull(debugString, "debugString");
return new UberCallOptions.Key<>(debugString, defaultValue);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment