Skip to content

Commit dc218b6

Browse files
authored
api: augment CreateSubchannelArgs with custom options (grpc#5640)
* api: augment CreateSubchannelArgs with custom options * added unit tests * added ExperimentalApi anntation with tracking issue
1 parent d530641 commit dc218b6

2 files changed

Lines changed: 149 additions & 3 deletions

File tree

api/src/main/java/io/grpc/LoadBalancer.java

Lines changed: 106 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -670,12 +670,14 @@ public static final class CreateSubchannelArgs {
670670
private final List<EquivalentAddressGroup> addrs;
671671
private final Attributes attrs;
672672
private final SubchannelStateListener stateListener;
673+
private final Object[][] customOptions;
673674

674675
private CreateSubchannelArgs(
675-
List<EquivalentAddressGroup> addrs, Attributes attrs,
676+
List<EquivalentAddressGroup> addrs, Attributes attrs, Object[][] customOptions,
676677
SubchannelStateListener stateListener) {
677678
this.addrs = checkNotNull(addrs, "addresses are not set");
678679
this.attrs = checkNotNull(attrs, "attrs");
680+
this.customOptions = checkNotNull(customOptions, "customOptions");
679681
this.stateListener = checkNotNull(stateListener, "SubchannelStateListener is not set");
680682
}
681683

@@ -693,6 +695,22 @@ public Attributes getAttributes() {
693695
return attrs;
694696
}
695697

698+
/**
699+
* Get the value for a custom option or its inherent default.
700+
*
701+
* @param key Key identifying option
702+
*/
703+
@SuppressWarnings("unchecked")
704+
public <T> T getOption(Key<T> key) {
705+
Preconditions.checkNotNull(key, "key");
706+
for (int i = 0; i < customOptions.length; i++) {
707+
if (key.equals(customOptions[i][0])) {
708+
return (T) customOptions[i][1];
709+
}
710+
}
711+
return key.defaultValue;
712+
}
713+
696714
/**
697715
* Returns the state listener.
698716
*/
@@ -744,13 +762,45 @@ public boolean equals(Object other) {
744762

745763
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
746764
public static final class Builder {
765+
747766
private List<EquivalentAddressGroup> addrs;
748767
private Attributes attrs = Attributes.EMPTY;
749768
private SubchannelStateListener stateListener;
769+
private Object[][] customOptions = new Object[0][2];
750770

751771
Builder() {
752772
}
753773

774+
/**
775+
* Add a custom option. Any existing value for the key is overwritten.
776+
*
777+
* <p>This is an <strong>optional</strong> property.
778+
*
779+
* @param key the option key
780+
* @param value the option value
781+
*/
782+
public <T> Builder addOption(Key<T> key, T value) {
783+
Preconditions.checkNotNull(key, "key");
784+
Preconditions.checkNotNull(value, "value");
785+
786+
int existingIdx = -1;
787+
for (int i = 0; i < customOptions.length; i++) {
788+
if (key.equals(customOptions[i][0])) {
789+
existingIdx = i;
790+
break;
791+
}
792+
}
793+
794+
if (existingIdx == -1) {
795+
Object[][] newCustomOptions = new Object[customOptions.length + 1][2];
796+
System.arraycopy(customOptions, 0, newCustomOptions, 0, customOptions.length);
797+
customOptions = newCustomOptions;
798+
existingIdx = customOptions.length - 1;
799+
}
800+
customOptions[existingIdx] = new Object[]{key, value};
801+
return this;
802+
}
803+
754804
/**
755805
* The addresses to connect to. All addresses are considered equivalent and will be tried
756806
* in the order they are provided.
@@ -773,7 +823,7 @@ public Builder setAddresses(List<EquivalentAddressGroup> addrs) {
773823
this.addrs = Collections.unmodifiableList(new ArrayList<>(addrs));
774824
return this;
775825
}
776-
826+
777827
/**
778828
* Attributes provided here will be included in {@link Subchannel#getAttributes}.
779829
*
@@ -800,7 +850,60 @@ public Builder setStateListener(SubchannelStateListener listener) {
800850
* Creates a new args object.
801851
*/
802852
public CreateSubchannelArgs build() {
803-
return new CreateSubchannelArgs(addrs, attrs, stateListener);
853+
return new CreateSubchannelArgs(addrs, attrs, customOptions, stateListener);
854+
}
855+
}
856+
857+
/**
858+
* Key for a key-value pair. Uses reference equality.
859+
*/
860+
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
861+
public static final class Key<T> {
862+
863+
private final String debugString;
864+
private final T defaultValue;
865+
866+
private Key(String debugString, T defaultValue) {
867+
this.debugString = debugString;
868+
this.defaultValue = defaultValue;
869+
}
870+
871+
/**
872+
* Factory method for creating instances of {@link Key}. The default value of the key is
873+
* {@code null}.
874+
*
875+
* @param debugString a debug string that describes this key.
876+
* @param <T> Key type
877+
* @return Key object
878+
*/
879+
public static <T> Key<T> create(String debugString) {
880+
Preconditions.checkNotNull(debugString, "debugString");
881+
return new Key<>(debugString, /*defaultValue=*/ null);
882+
}
883+
884+
/**
885+
* Factory method for creating instances of {@link Key}.
886+
*
887+
* @param debugString a debug string that describes this key.
888+
* @param defaultValue default value to return when value for key not set
889+
* @param <T> Key type
890+
* @return Key object
891+
*/
892+
public static <T> Key<T> createWithDefault(String debugString, T defaultValue) {
893+
Preconditions.checkNotNull(debugString, "debugString");
894+
return new Key<>(debugString, defaultValue);
895+
}
896+
897+
/**
898+
* Returns the user supplied default value for this key.
899+
*/
900+
public T getDefault() {
901+
return defaultValue;
902+
}
903+
904+
@Override
905+
public String toString() {
906+
return debugString;
804907
}
805908
}
806909
}

api/src/test/java/io/grpc/LoadBalancerTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,49 @@ public void subchannel_getAddresses_throwsOnTwoAddrs() {
225225
}.getAddresses();
226226
}
227227

228+
@Test
229+
public void createSubchannelArgs_option_keyOps() {
230+
CreateSubchannelArgs.Key<String> testKey = CreateSubchannelArgs.Key.create("test-key");
231+
String testValue = "test-value";
232+
CreateSubchannelArgs.Key<String> testWithDefaultKey = CreateSubchannelArgs.Key
233+
.createWithDefault("test-key", testValue);
234+
CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder()
235+
.setAddresses(eag)
236+
.setAttributes(attrs)
237+
.setStateListener(subchannelStateListener)
238+
.build();
239+
assertThat(args.getOption(testKey)).isNull();
240+
assertThat(args.getOption(testWithDefaultKey)).isSameInstanceAs(testValue);
241+
}
242+
243+
@Test
244+
public void createSubchannelArgs_option_addGet() {
245+
String testValue = "test-value";
246+
CreateSubchannelArgs.Key<String> testKey = CreateSubchannelArgs.Key.create("test-key");
247+
CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder()
248+
.setAddresses(eag)
249+
.setAttributes(attrs)
250+
.setStateListener(subchannelStateListener)
251+
.addOption(testKey, testValue)
252+
.build();
253+
assertThat(args.getOption(testKey)).isEqualTo(testValue);
254+
}
255+
256+
@Test
257+
public void createSubchannelArgs_option_lastOneWins() {
258+
String testValue1 = "test-value-1";
259+
String testValue2 = "test-value-2";
260+
CreateSubchannelArgs.Key<String> testKey = CreateSubchannelArgs.Key.create("test-key");
261+
CreateSubchannelArgs args = CreateSubchannelArgs.newBuilder()
262+
.setAddresses(eag)
263+
.setAttributes(attrs)
264+
.setStateListener(subchannelStateListener)
265+
.addOption(testKey, testValue1)
266+
.addOption(testKey, testValue2)
267+
.build();
268+
assertThat(args.getOption(testKey)).isEqualTo(testValue2);
269+
}
270+
228271
@Deprecated
229272
@Test
230273
public void handleResolvedAddressGroups_delegatesToHandleResolvedAddresses() {

0 commit comments

Comments
 (0)