Skip to content

Commit

Permalink
Support dynamic properties in HTTP announcement
Browse files Browse the repository at this point in the history
Extend discovery binder's bindHttpAnnouncement's capabilities by
allowing bindings of non-constant properties that require Guice
injection to be calculated.
  • Loading branch information
findepi committed May 6, 2024
1 parent a697dbc commit cfede98
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,31 @@
*/
package io.airlift.discovery.client;

import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.inject.Binder;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.Scopes;
import com.google.inject.TypeLiteral;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.multibindings.Multibinder;
import com.google.inject.name.Named;
import com.google.inject.util.Providers;
import io.airlift.discovery.client.ServiceAnnouncement.ServiceAnnouncementBuilder;

import java.lang.annotation.Annotation;
import java.util.Map;

import static com.google.inject.multibindings.MapBinder.newMapBinder;
import static com.google.inject.multibindings.Multibinder.newSetBinder;
import static com.google.inject.name.Names.named;
import static io.airlift.configuration.ConfigBinder.configBinder;
import static io.airlift.discovery.client.ServiceAnnouncement.serviceAnnouncement;
import static io.airlift.discovery.client.ServiceTypes.serviceType;
import static java.util.Objects.requireNonNull;
import static java.util.UUID.randomUUID;

public class DiscoveryBinder
{
Expand Down Expand Up @@ -84,11 +96,12 @@ public <T extends ServiceAnnouncement> void bindServiceAnnouncement(Class<? exte
serviceAnnouncementBinder.addBinding().toProvider(announcementProviderClass);
}

public ServiceAnnouncementBuilder bindHttpAnnouncement(String type)
public HttpServiceAnnouncementBuilder bindHttpAnnouncement(String type)
{
ServiceAnnouncementBuilder serviceAnnouncementBuilder = serviceAnnouncement(type);
bindServiceAnnouncement(new HttpAnnouncementProvider(serviceAnnouncementBuilder));
return serviceAnnouncementBuilder;
Named annotation = named("http-announcement.%s.%s".formatted(type, randomUUID()));
MapBinder<String, String> propertiesBinder = newMapBinder(binder, String.class, String.class, annotation);
bindServiceAnnouncement(new HttpAnnouncementProvider(type, annotation));
return new HttpServiceAnnouncementBuilder(propertiesBinder);
}

public void bindHttpSelector(String type)
Expand All @@ -104,15 +117,90 @@ public void bindHttpSelector(ServiceType serviceType)
binder.bind(HttpServiceSelector.class).annotatedWith(serviceType).toProvider(new HttpServiceSelectorProvider(serviceType.value())).in(Scopes.SINGLETON);
}

public static class HttpServiceAnnouncementBuilder
{
private final MapBinder<String, String> propertiesBinder;

public HttpServiceAnnouncementBuilder(MapBinder<String, String> propertiesBinder)
{
this.propertiesBinder = propertiesBinder;
}

public PropertyBinding bindProperty(String key)
{
requireNonNull(key, "key is null");
return new PropertyBinding()
{
@Override
public HttpServiceAnnouncementBuilder toProvider(Provider<String> provider)
{
propertiesBinder.addBinding(key).toProvider(provider);
return HttpServiceAnnouncementBuilder.this;
}

@Override
public HttpServiceAnnouncementBuilder toProvider(Key<? extends Provider<String>> providerKey)
{
propertiesBinder.addBinding(key).toProvider(providerKey);
return HttpServiceAnnouncementBuilder.this;
}
};
}

@CanIgnoreReturnValue
public HttpServiceAnnouncementBuilder addProperty(String key, String value)
{
bindProperty(key).toInstance(value);
return this;
}

@CanIgnoreReturnValue
public HttpServiceAnnouncementBuilder addProperties(Map<String, String> properties)
{
properties.forEach(this::addProperty);
return this;
}

public interface PropertyBinding
{
@CanIgnoreReturnValue
default HttpServiceAnnouncementBuilder toInstance(String value)
{
return toProvider(Providers.of(requireNonNull(value, "value is null")));
}

@CanIgnoreReturnValue
HttpServiceAnnouncementBuilder toProvider(Provider<String> provider);

@CanIgnoreReturnValue
default HttpServiceAnnouncementBuilder toProvider(Class<? extends Provider<String>> providerType)
{
return toProvider(Key.get(providerType));
}

@CanIgnoreReturnValue
HttpServiceAnnouncementBuilder toProvider(Key<? extends Provider<String>> providerKey);
}
}

static class HttpAnnouncementProvider
implements Provider<ServiceAnnouncement>
{
private final ServiceAnnouncementBuilder builder;
private final String type;
private final Annotation annotation;
private Injector injector;
private AnnouncementHttpServerInfo httpServerInfo;

public HttpAnnouncementProvider(ServiceAnnouncementBuilder serviceAnnouncementBuilder)
public HttpAnnouncementProvider(String type, Annotation annotation)
{
builder = serviceAnnouncementBuilder;
this.type = type;
this.annotation = annotation;
}

@Inject
public void setInjector(Injector injector)
{
this.injector = injector;
}

@Inject
Expand All @@ -124,6 +212,9 @@ public void setAnnouncementHttpServerInfo(AnnouncementHttpServerInfo httpServerI
@Override
public ServiceAnnouncement get()
{
ServiceAnnouncementBuilder builder = serviceAnnouncement(type);
builder.addProperties(injector.getInstance(Key.get(new TypeLiteral<Map<String, String>>() {}, annotation)));

if (httpServerInfo.getHttpUri() != null) {
builder.addProperty("http", httpServerInfo.getHttpUri().toString());
builder.addProperty("http-external", httpServerInfo.getHttpExternalUri().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
package io.airlift.discovery.client;

import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;
import io.airlift.discovery.client.testing.TestingDiscoveryModule;
import org.testng.annotations.Test;

import java.net.URI;
import java.util.Set;
import java.util.UUID;

import static com.google.common.collect.MoreCollectors.onlyElement;
import static io.airlift.discovery.client.DiscoveryBinder.discoveryBinder;
Expand Down Expand Up @@ -142,6 +145,38 @@ public void testHttpAnnouncementWithCustomProperties()
assertAnnouncement(announcements, announcement);
}

@Test
public void testHttpAnnouncementWithCustomProvidedProperties()
{
StaticAnnouncementHttpServerInfoImpl httpServerInfo = new StaticAnnouncementHttpServerInfoImpl(
URI.create("http://127.0.0.1:4444"),
URI.create("http://example.com:4444"),
URI.create("https://127.0.0.1:4444"),
URI.create("https://example.com:4444"));
String randomValue = UUID.randomUUID().toString();

Injector injector = Guice.createInjector(
new TestingDiscoveryModule(),
binder -> {
binder.bind(AnnouncementHttpServerInfo.class).toInstance(httpServerInfo);
discoveryBinder(binder).bindHttpAnnouncement("apple")
.bindProperty("instance-property").toInstance("my-instance")
.bindProperty("provided-by-instance").toProvider(() -> "provided-constant: " + randomValue)
.bindProperty("provided-by-injected").toProvider(StringPropertyProvider.class);
});

Set<ServiceAnnouncement> announcements = injector.getInstance(Key.get(new TypeLiteral<Set<ServiceAnnouncement>>() {}));
assertAnnouncement(announcements, serviceAnnouncement("apple")
.addProperty("instance-property", "my-instance")
.addProperty("provided-by-instance", "provided-constant: " + randomValue)
.addProperty("provided-by-injected", "concatenated: http://127.0.0.1:4444 https://127.0.0.1:4444")
.addProperty("http", "http://127.0.0.1:4444")
.addProperty("http-external", "http://example.com:4444")
.addProperty("https", "https://127.0.0.1:4444")
.addProperty("https-external", "https://example.com:4444")
.build());
}

private void assertAnnouncement(Set<ServiceAnnouncement> actualAnnouncements, ServiceAnnouncement expected)
{
assertNotNull(actualAnnouncements);
Expand All @@ -150,4 +185,22 @@ private void assertAnnouncement(Set<ServiceAnnouncement> actualAnnouncements, Se
assertEquals(announcement.getType(), expected.getType());
assertEquals(announcement.getProperties(), expected.getProperties());
}

public static class StringPropertyProvider
implements Provider<String>
{
private final AnnouncementHttpServerInfo httpServerInfo;

@Inject
public StringPropertyProvider(AnnouncementHttpServerInfo httpServerInfo)
{
this.httpServerInfo = httpServerInfo;
}

@Override
public String get()
{
return "concatenated: %s %s".formatted(httpServerInfo.getHttpUri(), httpServerInfo.getHttpsUri());
}
}
}

0 comments on commit cfede98

Please sign in to comment.