diff --git a/api/src/main/java/io/grpc/ClientStreamTracer.java b/api/src/main/java/io/grpc/ClientStreamTracer.java
index b0f5263a09..eb6ffb19b0 100644
--- a/api/src/main/java/io/grpc/ClientStreamTracer.java
+++ b/api/src/main/java/io/grpc/ClientStreamTracer.java
@@ -16,6 +16,9 @@
package io.grpc;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.MoreObjects;
import io.grpc.Grpc;
import javax.annotation.concurrent.ThreadSafe;
@@ -85,17 +88,102 @@ public abstract class ClientStreamTracer extends StreamTracer {
/**
* Information about a stream.
+ *
+ *
Note this class doesn't override {@code equals()} and {@code hashCode}, as is the case for
+ * {@link CallOptions}.
+ *
+ * @since 1.20.0
*/
- public abstract static class StreamInfo {
+ @ExperimentalApi("https://github.com/grpc/grpc-java/issues/2861")
+ public static final class StreamInfo {
+ private final Attributes transportAttrs;
+ private final CallOptions callOptions;
+
+ StreamInfo(Attributes transportAttrs, CallOptions callOptions) {
+ this.transportAttrs = checkNotNull(transportAttrs, "transportAttrs");
+ this.callOptions = checkNotNull(callOptions, "callOptions");
+ }
+
/**
* Returns the attributes of the transport that this stream was created on.
*/
@Grpc.TransportAttr
- public abstract Attributes getTransportAttrs();
+ public Attributes getTransportAttrs() {
+ return transportAttrs;
+ }
/**
* Returns the effective CallOptions of the call.
*/
- public abstract CallOptions getCallOptions();
+ public CallOptions getCallOptions() {
+ return callOptions;
+ }
+
+ /**
+ * Converts this StreamInfo into a new Builder.
+ *
+ * @since 1.21.0
+ */
+ public Builder toBuilder() {
+ Builder builder = new Builder();
+ builder.setTransportAttrs(transportAttrs);
+ builder.setCallOptions(callOptions);
+ return builder;
+ }
+
+ /**
+ * Creates an empty Builder.
+ *
+ * @since 1.21.0
+ */
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ @Override
+ public String toString() {
+ return MoreObjects.toStringHelper(this)
+ .add("transportAttrs", transportAttrs)
+ .add("callOptions", callOptions)
+ .toString();
+ }
+
+ /**
+ * Builds {@link StreamInfo} objects.
+ *
+ * @since 1.21.0
+ */
+ public static final class Builder {
+ private Attributes transportAttrs = Attributes.EMPTY;
+ private CallOptions callOptions = CallOptions.DEFAULT;
+
+ Builder() {
+ }
+
+ /**
+ * Sets the attributes of the transport that this stream was created on. This field is
+ * optional.
+ */
+ @Grpc.TransportAttr
+ public Builder setTransportAttrs(Attributes transportAttrs) {
+ this.transportAttrs = checkNotNull(transportAttrs, "transportAttrs cannot be null");
+ return this;
+ }
+
+ /**
+ * Sets the effective CallOptions of the call. This field is optional.
+ */
+ public Builder setCallOptions(CallOptions callOptions) {
+ this.callOptions = checkNotNull(callOptions, "callOptions cannot be null");
+ return this;
+ }
+
+ /**
+ * Builds a new StreamInfo.
+ */
+ public StreamInfo build() {
+ return new StreamInfo(transportAttrs, callOptions);
+ }
+ }
}
}
diff --git a/core/src/main/java/io/grpc/internal/StatsTraceContext.java b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
index d58fddc84d..adb0b63ec8 100644
--- a/core/src/main/java/io/grpc/internal/StatsTraceContext.java
+++ b/core/src/main/java/io/grpc/internal/StatsTraceContext.java
@@ -53,17 +53,9 @@ public final class StatsTraceContext {
if (factories.isEmpty()) {
return NOOP;
}
- ClientStreamTracer.StreamInfo info = new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return transportAttrs;
- }
-
- @Override
- public CallOptions getCallOptions() {
- return callOptions;
- }
- };
+ ClientStreamTracer.StreamInfo info =
+ ClientStreamTracer.StreamInfo.newBuilder()
+ .setTransportAttrs(transportAttrs).setCallOptions(callOptions).build();
// This array will be iterated multiple times per RPC. Use primitive array instead of Collection
// so that for-each doesn't create an Iterator every time.
StreamTracer[] tracers = new StreamTracer[factories.size()];
diff --git a/core/src/test/java/io/grpc/ClientStreamTracerTest.java b/core/src/test/java/io/grpc/ClientStreamTracerTest.java
new file mode 100644
index 0000000000..2008a3de5c
--- /dev/null
+++ b/core/src/test/java/io/grpc/ClientStreamTracerTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2019, gRPC Authors All rights reserved.
+ *
+ * Licensed 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 io.grpc;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.util.concurrent.TimeUnit.MINUTES;
+
+import io.grpc.ClientStreamTracer.StreamInfo;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for the embedded classes in {@link ClientStreamTracer}. */
+@RunWith(JUnit4.class)
+public class ClientStreamTracerTest {
+ private static final Attributes.Key TRANSPORT_ATTR_KEY =
+ Attributes.Key.create("transport-attr-key");
+ private final CallOptions callOptions = CallOptions.DEFAULT.withDeadlineAfter(1, MINUTES);
+ private final Attributes transportAttrs =
+ Attributes.newBuilder().set(TRANSPORT_ATTR_KEY, "value").build();
+
+ @Test
+ public void streamInfo_empty() {
+ StreamInfo info = StreamInfo.newBuilder().build();
+ assertThat(info.getCallOptions()).isSameInstanceAs(CallOptions.DEFAULT);
+ assertThat(info.getTransportAttrs()).isSameInstanceAs(Attributes.EMPTY);
+ }
+
+ @Test
+ public void streamInfo_withInfo() {
+ StreamInfo info = StreamInfo.newBuilder()
+ .setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
+ assertThat(info.getCallOptions()).isSameInstanceAs(callOptions);
+ assertThat(info.getTransportAttrs()).isSameInstanceAs(transportAttrs);
+ }
+
+ @Test
+ public void streamInfo_noEquality() {
+ StreamInfo info1 = StreamInfo.newBuilder()
+ .setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
+ StreamInfo info2 = StreamInfo.newBuilder()
+ .setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
+
+ assertThat(info1).isNotSameInstanceAs(info2);
+ assertThat(info1).isNotEqualTo(info2);
+ }
+
+ @Test
+ public void streamInfo_toBuilder() {
+ StreamInfo info1 = StreamInfo.newBuilder()
+ .setCallOptions(callOptions).setTransportAttrs(transportAttrs).build();
+ StreamInfo info2 = info1.toBuilder().build();
+ assertThat(info2.getCallOptions()).isSameInstanceAs(callOptions);
+ assertThat(info2.getTransportAttrs()).isSameInstanceAs(transportAttrs);
+ }
+}
diff --git a/core/src/test/java/io/grpc/internal/CensusModulesTest.java b/core/src/test/java/io/grpc/internal/CensusModulesTest.java
index 6774be0125..4ea4e3efc1 100644
--- a/core/src/test/java/io/grpc/internal/CensusModulesTest.java
+++ b/core/src/test/java/io/grpc/internal/CensusModulesTest.java
@@ -107,17 +107,7 @@ public class CensusModulesTest {
private static final CallOptions CALL_OPTIONS =
CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue");
private static final ClientStreamTracer.StreamInfo STREAM_INFO =
- new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.EMPTY;
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ ClientStreamTracer.StreamInfo.newBuilder().build();
private static class StringInputStream extends InputStream {
final String string;
diff --git a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
index a65ee436ac..cd93ae54dc 100644
--- a/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
+++ b/core/src/test/java/io/grpc/internal/RetriableStreamTest.java
@@ -41,8 +41,6 @@ import static org.mockito.Mockito.when;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.MoreExecutors;
-import io.grpc.Attributes;
-import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.Codec;
import io.grpc.Compressor;
@@ -92,17 +90,7 @@ public class RetriableStreamTest {
private static final double BACKOFF_MULTIPLIER = 2D;
private static final double FAKE_RANDOM = .5D;
private static final ClientStreamTracer.StreamInfo STREAM_INFO =
- new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.EMPTY;
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ ClientStreamTracer.StreamInfo.newBuilder().build();
static {
RetriableStream.setRandom(
diff --git a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
index 4e4a5ff903..a995e735d0 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/GrpclbLoadBalancerTest.java
@@ -49,7 +49,6 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.util.Durations;
import com.google.protobuf.util.Timestamps;
import io.grpc.Attributes;
-import io.grpc.CallOptions;
import io.grpc.ChannelLogger;
import io.grpc.ClientStreamTracer;
import io.grpc.ConnectivityState;
@@ -187,17 +186,7 @@ public class GrpclbLoadBalancerTest {
}
});
private static final ClientStreamTracer.StreamInfo STREAM_INFO =
- new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.EMPTY;
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ ClientStreamTracer.StreamInfo.newBuilder().build();
private io.grpc.Server fakeLbServer;
@Captor
diff --git a/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java b/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
index 8f188e6309..34b0a8ea1a 100644
--- a/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
+++ b/grpclb/src/test/java/io/grpc/grpclb/TokenAttachingTracerFactoryTest.java
@@ -23,7 +23,6 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import io.grpc.Attributes;
-import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.Metadata;
import io.grpc.internal.GrpcAttributes;
@@ -50,20 +49,12 @@ public class TokenAttachingTracerFactoryTest {
@Test
public void hasToken() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(delegate);
- ClientStreamTracer.StreamInfo info = new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- Attributes eagAttrs = Attributes.newBuilder()
- .set(GrpclbConstants.TOKEN_ATTRIBUTE_KEY, "token0001").build();
- return Attributes.newBuilder()
- .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build();
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ Attributes eagAttrs = Attributes.newBuilder()
+ .set(GrpclbConstants.TOKEN_ATTRIBUTE_KEY, "token0001").build();
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
+ .setTransportAttrs(
+ Attributes.newBuilder().set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, eagAttrs).build())
+ .build();
Metadata headers = new Metadata();
// Preexisting token should be replaced
headers.put(GrpclbConstants.TOKEN_METADATA_KEY, "preexisting-token");
@@ -77,18 +68,11 @@ public class TokenAttachingTracerFactoryTest {
@Test
public void noToken() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(delegate);
- ClientStreamTracer.StreamInfo info = new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.newBuilder()
- .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build();
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
+ .setTransportAttrs(
+ Attributes.newBuilder()
+ .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build())
+ .build();
Metadata headers = new Metadata();
// Preexisting token should be removed
@@ -103,18 +87,12 @@ public class TokenAttachingTracerFactoryTest {
@Test
public void nullDelegate() {
TokenAttachingTracerFactory factory = new TokenAttachingTracerFactory(null);
- ClientStreamTracer.StreamInfo info = new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.newBuilder()
- .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build();
- }
+ ClientStreamTracer.StreamInfo info = ClientStreamTracer.StreamInfo.newBuilder()
+ .setTransportAttrs(
+ Attributes.newBuilder()
+ .set(GrpcAttributes.ATTR_CLIENT_EAG_ATTRS, Attributes.EMPTY).build())
+ .build();
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
Metadata headers = new Metadata();
ClientStreamTracer tracer = factory.newClientStreamTracer(info, headers);
diff --git a/xds/src/test/java/io/grpc/xds/XdsLoadReportStoreTest.java b/xds/src/test/java/io/grpc/xds/XdsLoadReportStoreTest.java
index c6e4b47ace..8f896f3214 100644
--- a/xds/src/test/java/io/grpc/xds/XdsLoadReportStoreTest.java
+++ b/xds/src/test/java/io/grpc/xds/XdsLoadReportStoreTest.java
@@ -28,8 +28,6 @@ import io.envoyproxy.envoy.api.v2.core.Locality;
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats;
import io.envoyproxy.envoy.api.v2.endpoint.ClusterStats.DroppedRequests;
import io.envoyproxy.envoy.api.v2.endpoint.UpstreamLocalityStats;
-import io.grpc.Attributes;
-import io.grpc.CallOptions;
import io.grpc.ClientStreamTracer;
import io.grpc.LoadBalancer.PickResult;
import io.grpc.LoadBalancer.Subchannel;
@@ -58,17 +56,7 @@ import org.mockito.MockitoAnnotations;
public class XdsLoadReportStoreTest {
private static final String SERVICE_NAME = "api.google.com";
private static final ClientStreamTracer.StreamInfo STREAM_INFO =
- new ClientStreamTracer.StreamInfo() {
- @Override
- public Attributes getTransportAttrs() {
- return Attributes.EMPTY;
- }
-
- @Override
- public CallOptions getCallOptions() {
- return CallOptions.DEFAULT;
- }
- };
+ ClientStreamTracer.StreamInfo.newBuilder().build();
private static final Locality TEST_LOCALITY = Locality.newBuilder()
.setRegion("test_region")
.setZone("test_zone")