From 62edbab4349e4b8941f9f2c5b9979d5cdd3307af Mon Sep 17 00:00:00 2001 From: Bingkun Guo Date: Thu, 24 Mar 2016 16:23:22 -0500 Subject: [PATCH] Conditional multi bind Usage example: ConditionalMultibind.create(props, binder, Animal.class) .conditionBinder("animal.type", Predicates.equalTo("cat"), Cat.class) .conditionBinder("animal.type", Predicates.equalTo("dog"), Dog.class); At binding time, this will check the value set for property "animal.type" in props. If the value is "cat", it will add a binding to Cat.class. If the value is "dog", it will add a binding to Dog.class. At ingestion time, you will get the items that satisfy their corresponding predicates by calling injector.getInstance(Key.get(new TypeLiteral>(){})) --- .../io/druid/guice/ConditionalMultibind.java | 244 +++++++++ .../druid/guice/ConditionalMultibindTest.java | 477 ++++++++++++++++++ 2 files changed, 721 insertions(+) create mode 100644 api/src/main/java/io/druid/guice/ConditionalMultibind.java create mode 100644 api/src/test/java/io/druid/guice/ConditionalMultibindTest.java diff --git a/api/src/main/java/io/druid/guice/ConditionalMultibind.java b/api/src/main/java/io/druid/guice/ConditionalMultibind.java new file mode 100644 index 000000000000..2846977944c2 --- /dev/null +++ b/api/src/main/java/io/druid/guice/ConditionalMultibind.java @@ -0,0 +1,244 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.guice; + +import com.google.common.base.Predicate; +import com.google.inject.Binder; +import com.google.inject.TypeLiteral; +import com.google.inject.multibindings.Multibinder; + +import java.lang.annotation.Annotation; +import java.util.Properties; + +/** + * Provides the ability to conditionally bind an item to a set. The condition is based on the value set in the + * runtime.properties. + * + * Usage example: + * + * ConditionalMultibind.create(props, binder, Animal.class) + * .addConditionBinding("animal.type", Predicates.equalTo("cat"), Cat.class) + * .addConditionBinding("animal.type", Predicates.equalTo("dog"), Dog.class); + * + * At binding time, this will check the value set for property "animal.type" in props. If the value is "cat", it will + * add a binding to Cat.class. If the value is "dog", it will add a binding to Dog.class. + * + * At injection time, you will get the items that satisfy their corresponding predicates by calling + * injector.getInstance(Key.get(new TypeLiteral>(){})) + */ +public class ConditionalMultibind +{ + + /** + * Create a ConditionalMultibind that resolves items to be added to the set at "binding" time. + * + * @param properties the runtime properties. + * @param binder the binder for the injector that is being configured. + * @param type the type that will be injected. + * @param interface type. + * + * @return An instance of ConditionalMultibind that can be used to add conditional bindings. + */ + public static ConditionalMultibind create(Properties properties, Binder binder, Class type) + { + return new ConditionalMultibind(properties, Multibinder.newSetBinder(binder, type)); + } + + /** + * Create a ConditionalMultibind that resolves items to be added to the set at "binding" time. + * + * @param properties the runtime properties. + * @param binder the binder for the injector that is being configured. + * @param type the type that will be injected. + * @param interface type. + * @param annotationType the binding annotation. + * + * @return An instance of ConditionalMultibind that can be used to add conditional bindings. + */ + public static ConditionalMultibind create( + Properties properties, + Binder binder, + Class type, + Class annotationType + ) + { + return new ConditionalMultibind(properties, Multibinder.newSetBinder(binder, type, annotationType)); + } + + /** + * Create a ConditionalMultibind that resolves items to be added to the set at "binding" time. + * + * @param properties the runtime properties. + * @param binder the binder for the injector that is being configured. + * @param type the type that will be injected. + * @param interface type. + * + * @return An instance of ConditionalMultibind that can be used to add conditional bindings. + */ + public static ConditionalMultibind create(Properties properties, Binder binder, TypeLiteral type) + { + return new ConditionalMultibind(properties, Multibinder.newSetBinder(binder, type)); + } + + /** + * Create a ConditionalMultibind that resolves items to be added to the set at "binding" time. + * + * @param properties the runtime properties. + * @param binder the binder for the injector that is being configured. + * @param type the type that will be injected. + * @param interface type. + * @param annotationType the binding annotation. + * + * @return An instance of ConditionalMultibind that can be used to add conditional bindings. + */ + public static ConditionalMultibind create( + Properties properties, + Binder binder, + TypeLiteral type, + Class annotationType + ) + { + return new ConditionalMultibind(properties, Multibinder.newSetBinder(binder, type, annotationType)); + } + + + private final Properties properties; + private final Multibinder multibinder; + + public ConditionalMultibind(Properties properties, Multibinder multibinder) + { + this.properties = properties; + this.multibinder = multibinder; + } + + /** + * Unconditionally bind target to the set. + * + * @param target the target class to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addBinding(Class target) + { + multibinder.addBinding().to(target); + return this; + } + + /** + * Unconditionally bind target to the set. + * + * @param target the target instance to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addBinding(T target) + { + multibinder.addBinding().toInstance(target); + return this; + } + + /** + * Unconditionally bind target to the set. + * + * @param target the target type to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addBinding(TypeLiteral target) + { + multibinder.addBinding().to(target); + return this; + } + + /** + * Conditionally bind target to the set. If "condition" returns true, add a binding to "target". + * + * @param property the property to inspect on + * @param condition the predicate used to verify whether to add a binding to "target" + * @param target the target class to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addConditionBinding( + String property, + Predicate condition, + Class target + ) + { + final String value = properties.getProperty(property); + if (value == null) { + return this; + } + if (condition.apply(value)) { + multibinder.addBinding().to(target); + } + return this; + } + + /** + * Conditionally bind target to the set. If "condition" returns true, add a binding to "target". + * + * @param property the property to inspect on + * @param condition the predicate used to verify whether to add a binding to "target" + * @param target the target instance to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addConditionBinding( + String property, + Predicate condition, + T target + ) + { + final String value = properties.getProperty(property); + if (value == null) { + return this; + } + if (condition.apply(value)) { + multibinder.addBinding().toInstance(target); + } + return this; + } + + /** + * Conditionally bind target to the set. If "condition" returns true, add a binding to "target". + * + * @param property the property to inspect on + * @param condition the predicate used to verify whether to add a binding to "target" + * @param target the target type to which it adds a binding. + * + * @return self to support a continuous syntax for adding more conditional bindings. + */ + public ConditionalMultibind addConditionBinding( + String property, + Predicate condition, + TypeLiteral target + ) + { + final String value = properties.getProperty(property); + if (value == null) { + return this; + } + if (condition.apply(value)) { + multibinder.addBinding().to(target); + } + return this; + } +} diff --git a/api/src/test/java/io/druid/guice/ConditionalMultibindTest.java b/api/src/test/java/io/druid/guice/ConditionalMultibindTest.java new file mode 100644 index 000000000000..6b9a23491d82 --- /dev/null +++ b/api/src/test/java/io/druid/guice/ConditionalMultibindTest.java @@ -0,0 +1,477 @@ +/* + * Licensed to Metamarkets Group Inc. (Metamarkets) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. Metamarkets 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 io.druid.guice; + +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableSet; +import com.google.inject.Binder; +import com.google.inject.BindingAnnotation; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Key; +import com.google.inject.Module; +import com.google.inject.TypeLiteral; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +/** + */ +public class ConditionalMultibindTest +{ + + private static final String ANIMAL_TYPE = "animal.type"; + + private Properties props; + + @Before + public void setUp() throws Exception + { + props = new Properties(); + } + + @Test + public void testMultiConditionalBind_cat() + { + props.setProperty("animal.type", "cat"); + + Injector injector = Guice.createInjector(new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("cat"), Cat.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("dog"), Dog.class); + } + }); + + Set animalSet = injector.getInstance(Key.get(new TypeLiteral>() + { + })); + + Assert.assertEquals(1, animalSet.size()); + Assert.assertEquals(animalSet, ImmutableSet.of(new Cat())); + } + + @Test + public void testMultiConditionalBind_cat_dog() + { + props.setProperty("animal.type", "pets"); + + Injector injector = Guice.createInjector(new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class); + } + }); + + Set animalSet = injector.getInstance(Key.get(new TypeLiteral>() + { + })); + + Assert.assertEquals(2, animalSet.size()); + Assert.assertEquals(animalSet, ImmutableSet.of(new Cat(), new Dog())); + } + + @Test + public void testMultiConditionalBind_cat_dog_non_continuous_syntax() + { + props.setProperty("animal.type", "pets"); + + Injector injector = Guice.createInjector(new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class); + + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class); + + } + }); + + Set animalSet = injector.getInstance(Key.get(new TypeLiteral>() + { + })); + + Assert.assertEquals(2, animalSet.size()); + Assert.assertEquals(animalSet, ImmutableSet.of(new Cat(), new Dog())); + } + + @Test + public void testMultiConditionalBind_multiple_modules() + { + props.setProperty("animal.type", "pets"); + + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class); + } + }, + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("not_match"), Tiger.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class); + } + } + ); + + Set animalSet = injector.getInstance(Key.get(new TypeLiteral>() + { + })); + + Assert.assertEquals(3, animalSet.size()); + Assert.assertEquals(animalSet, ImmutableSet.of(new Cat(), new Dog(), new Fish())); + } + + @Test + public void testMultiConditionalBind_multiple_modules_with_annotation() + { + props.setProperty("animal.type", "pets"); + + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class, SanDiego.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class); + } + }, + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class, SanDiego.class) + .addBinding(new Bird()) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Tiger.class); + + ConditionalMultibind.create(props, binder, Animal.class, SanJose.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class); + } + } + ); + + Set animalSet_1 = injector.getInstance(Key.get(new TypeLiteral>() + { + }, SanDiego.class)); + Assert.assertEquals(4, animalSet_1.size()); + Assert.assertEquals(animalSet_1, ImmutableSet.of(new Bird(), new Cat(), new Dog(), new Tiger())); + + Set animalSet_2 = injector.getInstance(Key.get(new TypeLiteral>() + { + }, SanJose.class)); + Assert.assertEquals(1, animalSet_2.size()); + Assert.assertEquals(animalSet_2, ImmutableSet.of(new Fish())); + } + + @Test + public void testMultiConditionalBind_inject() + { + props.setProperty("animal.type", "pets"); + + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addBinding(Bird.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Cat.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Dog.class); + } + }, + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, Animal.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("not_match"), Tiger.class) + .addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), Fish.class); + } + } + ); + + PetShotAvails shop = new PetShotAvails(); + injector.injectMembers(shop); + + Assert.assertEquals(4, shop.animals.size()); + Assert.assertEquals(shop.animals, ImmutableSet.of(new Bird(), new Cat(), new Dog(), new Fish())); + } + + @Test + public void testMultiConditionalBind_typeLiteral() + { + props.setProperty("animal.type", "pets"); + + final Set set1 = ImmutableSet.of(new Dog(), new Tiger()); + final Set set2 = ImmutableSet.of(new Cat(), new Fish()); + final Set set3 = ImmutableSet.of(new Cat()); + final Set union = new HashSet<>(); + union.addAll(set1); + union.addAll(set2); + + final Zoo zoo1 = new Zoo<>(set1); + final Zoo zoo2 = new Zoo<>(); + + Injector injector = Guice.createInjector( + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, + new TypeLiteral>() + { + } + ).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set1 + ).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set2); + + ConditionalMultibind.create(props, binder, + new TypeLiteral>() + { + } + ).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), zoo1); + } + }, + new Module() + { + @Override + public void configure(Binder binder) + { + ConditionalMultibind.create(props, binder, + new TypeLiteral>() + { + } + ).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), set3); + + ConditionalMultibind.create(props, binder, + new TypeLiteral>() + { + }, + SanDiego.class + ).addConditionBinding(ANIMAL_TYPE, Predicates.equalTo("pets"), union); + + ConditionalMultibind.create(props, binder, + new TypeLiteral>() + { + } + ).addBinding(new TypeLiteral>() + { + }); + + } + } + ); + + Set> actualAnimalSet = injector.getInstance(Key.get(new TypeLiteral>>() + { + })); + Assert.assertEquals(3, actualAnimalSet.size()); + Assert.assertEquals(ImmutableSet.of(set1, set2, set3), actualAnimalSet); + + actualAnimalSet = injector.getInstance(Key.get(new TypeLiteral>>() + { + }, SanDiego.class)); + Assert.assertEquals(1, actualAnimalSet.size()); + Assert.assertEquals(ImmutableSet.of(union), actualAnimalSet); + + final Set> actualZooSet = injector.getInstance(Key.get(new TypeLiteral>>() + { + })); + Assert.assertEquals(2, actualZooSet.size()); + Assert.assertEquals(ImmutableSet.of(zoo1, zoo2), actualZooSet); + } + + static abstract class Animal + { + private final String type; + + Animal(String type) + { + this.type = type; + } + + @Override + public String toString() + { + return "Animal{" + + "type='" + type + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Animal animal = (Animal) o; + + return type != null ? type.equals(animal.type) : animal.type == null; + } + + @Override + public int hashCode() + { + return type != null ? type.hashCode() : 0; + } + } + + static class PetShotAvails + { + @Inject + Set animals; + } + + static class Dog extends Animal + { + Dog() + { + super("dog"); + } + } + + static class Cat extends Animal + { + Cat() + { + super("cat"); + } + } + + static class Fish extends Animal + { + Fish() + { + super("fish"); + } + } + + static class Tiger extends Animal + { + Tiger() + { + super("tiger"); + } + } + + static class Bird extends Animal + { + Bird() + { + super("bird"); + } + } + + static class Zoo + { + Set animals; + + public Zoo() + { + animals = new HashSet<>(); + } + + public Zoo(Set animals) + { + this.animals = animals; + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + Zoo zoo = (Zoo) o; + + return animals != null ? animals.equals(zoo.animals) : zoo.animals == null; + } + + @Override + public int hashCode() + { + return animals != null ? animals.hashCode() : 0; + } + + @Override + public String toString() + { + return "Zoo{" + + "animals=" + animals + + '}'; + } + } + + @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @BindingAnnotation + @interface SanDiego + { + } + + @Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD}) + @Retention(RetentionPolicy.RUNTIME) + @BindingAnnotation + @interface SanJose + { + } + +}