org.apache.druid.guice.PolyBind Maven / Gradle / Ivy
Show all versions of druid-processing Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.druid.guice;
import com.google.common.base.Preconditions;
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.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.binder.ScopedBindingBuilder;
import com.google.inject.multibindings.MapBinder;
import com.google.inject.util.Types;
import org.apache.druid.guice.annotations.PublicApi;
import org.apache.druid.java.util.common.StringUtils;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.ParameterizedType;
import java.util.Map;
import java.util.Properties;
/**
* Provides the ability to create "polymorphic" bindings where the polymorphism is actually just making a decision
* based on a value in Properties.
*
* The workflow is that you first create a choice by calling {@code createChoice()}. Then you create options using
* the binder returned by the {@code optionBinder()} method. Multiple different modules can call
* {@code optionBinder()} and all options will be reflected at injection time as long as equivalent interface
* {@code Key} objects are passed into the various methods.
*/
@PublicApi
public class PolyBind
{
/**
* Sets up a "choice" for the injector to resolve at injection time.
*
* @param binder the binder for the injector that is being configured
* @param property the property that will be checked to determine the implementation choice
* @param interfaceKey the interface that will be injected using this choice
* @param defaultKey the default instance to be injected if the property doesn't match a choice. Can be null
* @param interface type
* @return A ScopedBindingBuilder so that scopes can be added to the binding, if required.
*/
public static ScopedBindingBuilder createChoice(
Binder binder,
String property,
Key interfaceKey,
@Nullable Key defaultKey
)
{
ConfiggedProvider provider = new ConfiggedProvider<>(interfaceKey, property, defaultKey, null);
return binder.bind(interfaceKey).toProvider(provider);
}
/**
* @deprecated use {@link #createChoiceWithDefault(Binder, String, Key, String)}
* instead. {@code defaultKey} argument is ignored.
*/
@Deprecated
public static ScopedBindingBuilder createChoiceWithDefault(
Binder binder,
String property,
Key interfaceKey,
Key defaultKey,
String defaultPropertyValue
)
{
return createChoiceWithDefault(binder, property, interfaceKey, defaultPropertyValue);
}
/**
* Sets up a "choice" for the injector to resolve at injection time.
*
* @param binder the binder for the injector that is being configured
* @param property the property that will be checked to determine the implementation choice
* @param interfaceKey the interface that will be injected using this choice
* @param defaultPropertyValue the default property value to use if the property is not set.
* @param interface type
* @return A ScopedBindingBuilder so that scopes can be added to the binding, if required.
*/
public static ScopedBindingBuilder createChoiceWithDefault(
Binder binder,
String property,
Key interfaceKey,
String defaultPropertyValue
)
{
Preconditions.checkNotNull(defaultPropertyValue);
ConfiggedProvider provider = new ConfiggedProvider<>(interfaceKey, property, null, defaultPropertyValue);
return binder.bind(interfaceKey).toProvider(provider);
}
/**
* Binds an option for a specific choice. The choice must already be registered on the injector for this to work.
*
* @param binder the binder for the injector that is being configured
* @param interfaceKey the interface that will have an option added to it. This must equal the
* Key provided to createChoice
* @param interface type
* @return A MapBinder that can be used to create the actual option bindings.
*/
public static MapBinder optionBinder(Binder binder, Key interfaceKey)
{
final TypeLiteral interfaceType = interfaceKey.getTypeLiteral();
if (interfaceKey.getAnnotation() != null) {
return MapBinder.newMapBinder(binder, TypeLiteral.get(String.class), interfaceType, interfaceKey.getAnnotation());
} else if (interfaceKey.getAnnotationType() != null) {
Class annotationType = interfaceKey.getAnnotationType();
return MapBinder.newMapBinder(binder, TypeLiteral.get(String.class), interfaceType, annotationType);
} else {
return MapBinder.newMapBinder(binder, TypeLiteral.get(String.class), interfaceType);
}
}
static class ConfiggedProvider implements Provider
{
private final Key key;
private final String property;
@Nullable
private final Key defaultKey;
@Nullable
private final String defaultPropertyValue;
private Injector injector;
private Properties props;
ConfiggedProvider(
Key key,
String property,
@Nullable Key defaultKey,
@Nullable String defaultPropertyValue
)
{
this.key = key;
this.property = property;
this.defaultKey = defaultKey;
this.defaultPropertyValue = defaultPropertyValue;
}
@Inject
void configure(Injector injector, Properties props)
{
this.injector = injector;
this.props = props;
}
@Override
@SuppressWarnings("unchecked")
public T get()
{
final ParameterizedType mapType = Types.mapOf(
String.class, Types.newParameterizedType(Provider.class, key.getTypeLiteral().getType())
);
final Map> implsMap;
if (key.getAnnotation() != null) {
implsMap = (Map>) injector.getInstance(Key.get(mapType, key.getAnnotation()));
} else if (key.getAnnotationType() != null) {
implsMap = (Map>) injector.getInstance(Key.get(mapType, key.getAnnotationType()));
} else {
implsMap = (Map>) injector.getInstance(Key.get(mapType));
}
String implName = props.getProperty(property);
if (implName == null) {
if (defaultPropertyValue == null) {
if (defaultKey == null) {
throw new ProvisionException(StringUtils.format("Some value must be configured for [%s]", key));
}
return injector.getInstance(defaultKey);
}
implName = defaultPropertyValue;
}
final Provider provider = implsMap.get(implName);
if (provider == null) {
throw new ProvisionException(
StringUtils.format("Unknown provider [%s] of %s, known options [%s]", implName, key, implsMap.keySet())
);
}
return provider.get();
}
}
}