All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.druid.guice.PolyBind Maven / Gradle / Ivy

There is a newer version: 30.0.1
Show newest version
/*
 * 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy