From 26335c6112ed1a703671cf465649df9963fe01c2 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 1 Jun 2022 00:37:27 +0530 Subject: [PATCH 1/7] Temporary changes. --- tef-impl/pom.xml | 6 +++ .../java/flipkart/tef/TefGuiceModule.java | 7 +++ .../tef/execution/DefaultDataInjector.java | 1 + .../InjectDataGuiceMembersInjector.java | 36 +++++++++++++++ .../tef/guicebridge/SubclassesOfMatcher.java | 45 +++++++++++++++++++ .../TypeListenerForDataInjection.java | 43 ++++++++++++++++++ .../InjectDataGuiceMembersInjectorTest.java | 24 ++++++++++ 7 files changed, 162 insertions(+) create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java create mode 100644 tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java diff --git a/tef-impl/pom.xml b/tef-impl/pom.xml index dfbf34f..aa3b30e 100644 --- a/tef-impl/pom.xml +++ b/tef-impl/pom.xml @@ -41,6 +41,12 @@ test + + com.google.inject.extensions + guice-servlet + 4.0 + + \ No newline at end of file diff --git a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java index 82af049..3b259be 100644 --- a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java +++ b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java @@ -17,8 +17,12 @@ package flipkart.tef; import com.google.inject.AbstractModule; +import flipkart.tef.bizlogics.IBizlogic; import flipkart.tef.execution.DataInjector; import flipkart.tef.execution.DefaultDataInjector; +import flipkart.tef.execution.InjectableValueProvider; +import flipkart.tef.guicebridge.TypeListenerForDataInjection; +import flipkart.tef.guicebridge.SubclassesOfMatcher; /** * This class contains guice binding relevant for tef. @@ -30,5 +34,8 @@ public class TefGuiceModule extends AbstractModule { @Override protected void configure() { bind(DataInjector.class).to(DefaultDataInjector.class); + bind(InjectableValueProvider.class); + bindListener(new SubclassesOfMatcher(IBizlogic.class), new TypeListenerForDataInjection(null)); + //bindListener(Matchers.any(), new BizlogicTypeListener(null)); } } diff --git a/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java b/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java index cc8cac4..16edc6e 100644 --- a/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java +++ b/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java @@ -16,6 +16,7 @@ package flipkart.tef.execution; +import com.google.common.reflect.Reflection; import flipkart.tef.annotations.InjectData; import flipkart.tef.exception.TefExecutionException; diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java new file mode 100644 index 0000000..b4316d9 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java @@ -0,0 +1,36 @@ +package flipkart.tef.guicebridge; + +import com.google.inject.MembersInjector; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.execution.InjectableValueProvider; + +import java.lang.reflect.Field; + +/** + * MemberInjector to inject the actual value on fields annotated with @DataInject. + * @see TypeListenerForDataInjection for usage pattern + * + * Date: 31/05/22 + */ +public class InjectDataGuiceMembersInjector implements MembersInjector { + private final Field field; + private final String injectionName; + private final InjectableValueProvider valueProvider; + + // package-private to let only the type listener create an instance + InjectDataGuiceMembersInjector(Field field, String name, InjectableValueProvider valueProvider) { + this.field = field; + this.injectionName = name; + field.setAccessible(true); + this.valueProvider = valueProvider; + } + + @Override + public void injectMembers(T instance) { + try { + field.set(instance, valueProvider.getValueToInject(field.getType(), injectionName)); + } catch (IllegalAccessException | TefExecutionException e) { + throw new RuntimeException("Exception while injecting members in tef-guice bridge", e); + } + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java new file mode 100644 index 0000000..858abb5 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java @@ -0,0 +1,45 @@ +package flipkart.tef.guicebridge; + +import com.google.inject.matcher.AbstractMatcher; + +import java.io.Serializable; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Guice matcher for matching subclasses using TypeLiteral. + * + * Date: 31/05/22 + */ +public class SubclassesOfMatcher extends AbstractMatcher implements Serializable { + /* + Had to re-implement this class instead of using `Matchers.ofSubclass` since it was not based on TypeLiterals. + */ + private final Class superclass; + + public SubclassesOfMatcher(Class superclass) { + this.superclass = checkNotNull(superclass, "superclass"); + } + + @Override + public boolean matches(Object subclass) { + return superclass.isAssignableFrom(subclass.getClass()); + } + + @Override + public boolean equals(Object other) { + return other instanceof SubclassesOfMatcher && ((SubclassesOfMatcher) other).superclass.equals(superclass); + } + + @Override + public int hashCode() { + return 37 * superclass.hashCode(); + } + + @Override + public String toString() { + return "subclassesOf(" + superclass.getSimpleName() + ".class)"; + } + + private static final long serialVersionUID = 0; +} diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java new file mode 100644 index 0000000..6b90331 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java @@ -0,0 +1,43 @@ +package flipkart.tef.guicebridge; + +import com.google.inject.Inject; +import com.google.inject.TypeLiteral; +import com.google.inject.spi.TypeEncounter; +import com.google.inject.spi.TypeListener; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.execution.InjectableValueProvider; + +import java.lang.reflect.Field; + +/** + * TypeListener to process instances which are using the @InjectData annotation. + * + * Date: 31/05/22 + */ +public class TypeListenerForDataInjection implements TypeListener { + + private final InjectableValueProvider valueProvider; + + @Inject + public TypeListenerForDataInjection(InjectableValueProvider valueProvider) { + this.valueProvider = valueProvider; + } + + @Override + public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { + Class bizlogicClass = typeLiteral.getRawType(); + + while ((!bizlogicClass.equals(Object.class))) { + Field[] fields = bizlogicClass.getDeclaredFields(); + for (Field field : fields) { + if(field.isAnnotationPresent(InjectData.class)) { + InjectData injectable = field.getAnnotation(InjectData.class); + typeEncounter.register(new InjectDataGuiceMembersInjector<>(field, injectable.name(), valueProvider)); + } + } + bizlogicClass = bizlogicClass.getSuperclass(); + } + } + + +} diff --git a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java new file mode 100644 index 0000000..9267429 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java @@ -0,0 +1,24 @@ +package flipkart.tef.guicebridge; + +import flipkart.tef.TestTefContext; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.bizlogics.TefContext; +import org.junit.Assert; +import org.junit.Test; + +public class InjectDataGuiceMembersInjectorTest { + + @Test + public void testRequestScopeBasic(){ + TefContext tefContext = new TestTefContext(); + SimpleInterface instance = tefContext.getInjector().getInstance(SimpleInterface.class); + + Assert.assertNotNull("Data injection failed", instance.simpleData); + } + + static class SimpleData{} + + static class SimpleInterface { + @InjectData SimpleData simpleData; + } +} \ No newline at end of file From b1eae01cc3b5332569f7b7f0723e5dad3ad8394d Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 1 Jun 2022 04:04:08 +0530 Subject: [PATCH 2/7] Working draft --- pom.xml | 2 +- .../java/flipkart/tef/TefGuiceModule.java | 3 - .../tef/guicebridge/GuiceBridgeModule.java | 25 ++++ .../InjectDataGuiceMembersInjector.java | 23 ++-- .../tef/guicebridge/TefGuiceScope.java | 46 +++++++ .../tef/guicebridge/TefRequestScoped.java | 15 +++ .../TypeListenerForDataInjection.java | 17 ++- .../java/flipkart/tef/TestTefContext.java | 12 ++ .../InjectDataGuiceMembersInjectorTest.java | 116 ++++++++++++++++-- 9 files changed, 229 insertions(+), 30 deletions(-) create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java create mode 100644 tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java diff --git a/pom.xml b/pom.xml index 0875774..a172462 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ - 0.0.3-SNAPSHOT + 0.1.0-SNAPSHOT 19.0 4.2.3 diff --git a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java index 3b259be..2427897 100644 --- a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java +++ b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java @@ -34,8 +34,5 @@ public class TefGuiceModule extends AbstractModule { @Override protected void configure() { bind(DataInjector.class).to(DefaultDataInjector.class); - bind(InjectableValueProvider.class); - bindListener(new SubclassesOfMatcher(IBizlogic.class), new TypeListenerForDataInjection(null)); - //bindListener(Matchers.any(), new BizlogicTypeListener(null)); } } diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java new file mode 100644 index 0000000..fa06d77 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java @@ -0,0 +1,25 @@ +package flipkart.tef.guicebridge; + +import com.google.inject.AbstractModule; +import com.google.inject.Injector; +import com.google.inject.Provides; + +public class GuiceBridgeModule extends AbstractModule { + + private final TefGuiceScope scope; + + public GuiceBridgeModule() { + this.scope = new TefGuiceScope(); + } + + @Override + protected void configure() { + bindScope(TefRequestScoped.class, this.scope); + bind(TefGuiceScope.class).toInstance(scope); + } + + @Provides + public InjectDataGuiceMembersInjector provideInjectDataGuiceMembersInjector(Injector injector){ + return new InjectDataGuiceMembersInjector(injector); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java index b4316d9..b81ceda 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java @@ -1,5 +1,7 @@ package flipkart.tef.guicebridge; +import com.google.inject.Injector; +import com.google.inject.Key; import com.google.inject.MembersInjector; import flipkart.tef.exception.TefExecutionException; import flipkart.tef.execution.InjectableValueProvider; @@ -13,21 +15,28 @@ * Date: 31/05/22 */ public class InjectDataGuiceMembersInjector implements MembersInjector { - private final Field field; - private final String injectionName; - private final InjectableValueProvider valueProvider; + private Field field; + private String injectionName; + private final Injector injector; // package-private to let only the type listener create an instance - InjectDataGuiceMembersInjector(Field field, String name, InjectableValueProvider valueProvider) { + InjectDataGuiceMembersInjector(Injector injector){ + this.injector = injector; + } + + public void setField(Field field) { this.field = field; - this.injectionName = name; - field.setAccessible(true); - this.valueProvider = valueProvider; + } + + public void setInjectionName(String injectionName) { + this.injectionName = injectionName; } @Override public void injectMembers(T instance) { try { + InjectableValueProvider valueProvider = injector.getScopeBindings().get(TefRequestScoped.class) + .scope(Key.get(InjectableValueProvider.class), null).get(); field.set(instance, valueProvider.getValueToInject(field.getType(), injectionName)); } catch (IllegalAccessException | TefExecutionException e) { throw new RuntimeException("Exception while injecting members in tef-guice bridge", e); diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java new file mode 100644 index 0000000..89af82a --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java @@ -0,0 +1,46 @@ +package flipkart.tef.guicebridge; + +import com.google.inject.Key; +import com.google.inject.Provider; +import com.google.inject.Scope; +import flipkart.tef.execution.InjectableValueProvider; + +public class TefGuiceScope implements Scope, AutoCloseable { + + private final ThreadLocal threadLocal; + + public TefGuiceScope() { + threadLocal = new ThreadLocal<>(); + } + + @SuppressWarnings("unchecked") + public Provider scope(final Key key, final Provider creator) { + final String name = key.toString(); + return new Provider() { + public T get() { + if(key.getTypeLiteral().getRawType().isAssignableFrom(InjectableValueProvider.class)){ + return (T) threadLocal.get(); + } else { + return creator.get(); + } + } + + public String toString() { + return String.format("%s[%s]", creator, "TefRequestScoped"); + } + }; + } + + public String toString() { + return "TefRequestScoped"; + } + + public void open(InjectableValueProvider valueProvider){ + threadLocal.set(valueProvider); + } + + @Override + public void close() { + threadLocal.remove(); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java new file mode 100644 index 0000000..0344fde --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java @@ -0,0 +1,15 @@ +package flipkart.tef.guicebridge; + + +import com.google.inject.ScopeAnnotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@ScopeAnnotation +public @interface TefRequestScoped { +} diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java index 6b90331..c2282fd 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java @@ -1,6 +1,8 @@ package flipkart.tef.guicebridge; import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; @@ -16,13 +18,7 @@ */ public class TypeListenerForDataInjection implements TypeListener { - private final InjectableValueProvider valueProvider; - - @Inject - public TypeListenerForDataInjection(InjectableValueProvider valueProvider) { - this.valueProvider = valueProvider; - } - + @SuppressWarnings({"rawtypes", "unchecked"}) @Override public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) { Class bizlogicClass = typeLiteral.getRawType(); @@ -32,12 +28,13 @@ public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) for (Field field : fields) { if(field.isAnnotationPresent(InjectData.class)) { InjectData injectable = field.getAnnotation(InjectData.class); - typeEncounter.register(new InjectDataGuiceMembersInjector<>(field, injectable.name(), valueProvider)); + InjectDataGuiceMembersInjector membersInjector = typeEncounter.getProvider(InjectDataGuiceMembersInjector.class).get(); + membersInjector.setField(field); + membersInjector.setInjectionName(injectable.name()); + typeEncounter.register(membersInjector); } } bizlogicClass = bizlogicClass.getSuperclass(); } } - - } diff --git a/tef-impl/src/test/java/flipkart/tef/TestTefContext.java b/tef-impl/src/test/java/flipkart/tef/TestTefContext.java index 17b2bd5..5258cb9 100644 --- a/tef-impl/src/test/java/flipkart/tef/TestTefContext.java +++ b/tef-impl/src/test/java/flipkart/tef/TestTefContext.java @@ -16,10 +16,14 @@ package flipkart.tef; +import com.google.inject.AbstractModule; import com.google.inject.Guice; +import com.google.inject.Injector; import flipkart.tef.bizlogics.TefContext; import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; /** * This class is used for @@ -31,4 +35,12 @@ public class TestTefContext extends TefContext { public TestTefContext() { super(new HashMap<>(), Guice.createInjector(new TestGuiceModule()), System.out::println); } + + public TestTefContext(AbstractModule... modules) { + super(new HashMap<>(), Guice.createInjector(modules), System.out::println); + } + + public TestTefContext(Map additionalContext, Injector injector, Consumer exceptionLogger) { + super(additionalContext, injector, exceptionLogger); + } } diff --git a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java index 9267429..0fe55bf 100644 --- a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java +++ b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java @@ -1,24 +1,122 @@ package flipkart.tef.guicebridge; -import flipkart.tef.TestTefContext; +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Inject; +import com.google.inject.Injector; +import com.google.inject.matcher.Matchers; import flipkart.tef.annotations.InjectData; -import flipkart.tef.bizlogics.TefContext; -import org.junit.Assert; +import flipkart.tef.bizlogics.DataAdapterKey; +import flipkart.tef.bizlogics.DataAdapterResult; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.execution.DataContext; +import flipkart.tef.execution.InjectableValueProvider; +import org.junit.Ignore; import org.junit.Test; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + public class InjectDataGuiceMembersInjectorTest { + @Ignore @Test - public void testRequestScopeBasic(){ - TefContext tefContext = new TestTefContext(); - SimpleInterface instance = tefContext.getInjector().getInstance(SimpleInterface.class); + public void testRequestScopeBasic() { + + Injector rootInjector = Guice.createInjector(new GuiceBridgeModule(), new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new TypeListenerForDataInjection()); + } + }); + + + new TesterThread(rootInjector).run(); + new TesterThread(rootInjector).run(); + } + + @Ignore + @Test + public void testRequestScopeThreaded() throws InterruptedException { + + Injector rootInjector = Guice.createInjector(new GuiceBridgeModule(), new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new TypeListenerForDataInjection()); + } + }); + + List threads = new ArrayList<>(); + + for (int i = 0; i < 100; i++) { + Thread thread = new Thread(new TesterThread(rootInjector)); + threads.add(thread); + thread.start(); + } + + for (Thread thread : threads) { + thread.join(); + } + } + + static class TesterThread implements Runnable { + + Injector rootInjector; + + public TesterThread(Injector rootInjector) { + this.rootInjector = rootInjector; + } + + @Override + public void run() { + Long threadId = Thread.currentThread().getId(); + try (TefGuiceScope scope = rootInjector.getInstance(TefGuiceScope.class)) { + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new SimpleData())); + dataContext.put(new DataAdapterResult(threadId)); + InjectableValueProvider valueProvider = new InjectableValueProvider() { + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + return dataContext.get(new DataAdapterKey<>(name, fieldType)); + } + }; + + scope.open(valueProvider); + SimpleInterface result = rootInjector.getInstance(SampleCallable.class).call(); + assertNotNull("Data injection failed", result.simpleData); + assertEquals(threadId, result.threadId); + } + } + } + + @TefRequestScoped + static class SampleCallable implements Callable { + private SimpleInterface simpleInterface; + + @Inject + public SampleCallable(SimpleInterface simpleInterface) { + this.simpleInterface = simpleInterface; + } - Assert.assertNotNull("Data injection failed", instance.simpleData); + @Override + public SimpleInterface call() + { + return simpleInterface; + } } static class SimpleData{} - static class SimpleInterface { - @InjectData SimpleData simpleData; + static class SimpleInterface implements Serializable { + @InjectData + SimpleData simpleData; + @InjectData + Long threadId; } } \ No newline at end of file From e477c7c53327d7e04bbef99c3463132dc9c508de Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 1 Jun 2022 14:52:52 +0530 Subject: [PATCH 3/7] Tests --- .../java/flipkart/tef/TefGuiceModule.java | 4 - .../tef/guicebridge/TefGuiceScope.java | 4 +- .../TypeListenerForDataInjection.java | 4 - .../InjectDataGuiceMembersInjectorTest.java | 76 ++++++++++++++++++- 4 files changed, 76 insertions(+), 12 deletions(-) diff --git a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java index 2427897..82af049 100644 --- a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java +++ b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java @@ -17,12 +17,8 @@ package flipkart.tef; import com.google.inject.AbstractModule; -import flipkart.tef.bizlogics.IBizlogic; import flipkart.tef.execution.DataInjector; import flipkart.tef.execution.DefaultDataInjector; -import flipkart.tef.execution.InjectableValueProvider; -import flipkart.tef.guicebridge.TypeListenerForDataInjection; -import flipkart.tef.guicebridge.SubclassesOfMatcher; /** * This class contains guice binding relevant for tef. diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java index 89af82a..01b68d9 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java @@ -1,5 +1,6 @@ package flipkart.tef.guicebridge; +import com.google.common.base.Preconditions; import com.google.inject.Key; import com.google.inject.Provider; import com.google.inject.Scope; @@ -15,10 +16,10 @@ public TefGuiceScope() { @SuppressWarnings("unchecked") public Provider scope(final Key key, final Provider creator) { - final String name = key.toString(); return new Provider() { public T get() { if(key.getTypeLiteral().getRawType().isAssignableFrom(InjectableValueProvider.class)){ + Preconditions.checkState(threadLocal.get() != null, "A scoping block is missing"); return (T) threadLocal.get(); } else { return creator.get(); @@ -36,6 +37,7 @@ public String toString() { } public void open(InjectableValueProvider valueProvider){ + Preconditions.checkState(threadLocal.get() == null, "A scoping block is already in progress"); threadLocal.set(valueProvider); } diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java index c2282fd..0bb6cec 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java @@ -1,13 +1,9 @@ package flipkart.tef.guicebridge; -import com.google.inject.Inject; -import com.google.inject.Injector; -import com.google.inject.Provider; import com.google.inject.TypeLiteral; import com.google.inject.spi.TypeEncounter; import com.google.inject.spi.TypeListener; import flipkart.tef.annotations.InjectData; -import flipkart.tef.execution.InjectableValueProvider; import java.lang.reflect.Field; diff --git a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java index 0fe55bf..fddc2e1 100644 --- a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java +++ b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java @@ -4,6 +4,7 @@ import com.google.inject.Guice; import com.google.inject.Inject; import com.google.inject.Injector; +import com.google.inject.ProvisionException; import com.google.inject.matcher.Matchers; import flipkart.tef.annotations.InjectData; import flipkart.tef.bizlogics.DataAdapterKey; @@ -11,7 +12,7 @@ import flipkart.tef.exception.TefExecutionException; import flipkart.tef.execution.DataContext; import flipkart.tef.execution.InjectableValueProvider; -import org.junit.Ignore; +import org.junit.Assert; import org.junit.Test; import java.io.Serializable; @@ -21,10 +22,10 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; public class InjectDataGuiceMembersInjectorTest { - @Ignore @Test public void testRequestScopeBasic() { @@ -40,7 +41,6 @@ protected void configure() { new TesterThread(rootInjector).run(); } - @Ignore @Test public void testRequestScopeThreaded() throws InterruptedException { @@ -64,6 +64,76 @@ protected void configure() { } } + @Test + public void testRequestScopeWithoutEnteringScope() { + + Injector rootInjector = Guice.createInjector(new GuiceBridgeModule(), new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new TypeListenerForDataInjection()); + } + }); + + Long threadId = Thread.currentThread().getId(); + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new SimpleData())); + dataContext.put(new DataAdapterResult(threadId)); + InjectableValueProvider valueProvider = new InjectableValueProvider() { + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + return dataContext.get(new DataAdapterKey<>(name, fieldType)); + } + }; + try { + SimpleInterface result = rootInjector.getInstance(SampleCallable.class).call(); + Assert.fail("Injection should have failed"); + } catch (ProvisionException e) { + assertTrue(e.getCause() instanceof IllegalStateException); + assertEquals("A scoping block is missing", e.getCause().getMessage()); + } + } + + @Test + public void testRequestScopeWithEnteringScopeTwice() { + + Injector rootInjector = Guice.createInjector(new GuiceBridgeModule(), new AbstractModule() { + @Override + protected void configure() { + bindListener(Matchers.any(), new TypeListenerForDataInjection()); + } + }); + + Long threadId = Thread.currentThread().getId(); + try (TefGuiceScope scope = rootInjector.getInstance(TefGuiceScope.class)) { + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new SimpleData())); + dataContext.put(new DataAdapterResult(threadId)); + InjectableValueProvider valueProvider = new InjectableValueProvider() { + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + return dataContext.get(new DataAdapterKey<>(name, fieldType)); + } + }; + + InjectableValueProvider valueProvider2 = new InjectableValueProvider() { + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + return dataContext.get(new DataAdapterKey<>(name, fieldType)); + } + }; + + scope.open(valueProvider); + try { + scope.open(valueProvider2); + } catch (IllegalStateException e) { + assertEquals("A scoping block is already in progress", e.getMessage()); + } + } + } + static class TesterThread implements Runnable { Injector rootInjector; From 15f0ec017551f69f7784e95cf7452eec5a5f6d7e Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 1 Jun 2022 15:49:19 +0530 Subject: [PATCH 4/7] Remove un-necessary dependency --- tef-impl/pom.xml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tef-impl/pom.xml b/tef-impl/pom.xml index aa3b30e..29d00cb 100644 --- a/tef-impl/pom.xml +++ b/tef-impl/pom.xml @@ -40,13 +40,6 @@ 4.12 test - - - com.google.inject.extensions - guice-servlet - 4.0 - - \ No newline at end of file From 12b6426071c8f6eee700e73a83a6174722d458e1 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 1 Jun 2022 17:20:28 +0530 Subject: [PATCH 5/7] Fix and Test for subtype matcher. --- .../tef/guicebridge/GuiceBridgeModule.java | 4 + ....java => SubclassOrAnnotationMatcher.java} | 14 ++-- .../tef/guicebridge/TefGuiceScope.java | 8 ++ .../tef/guicebridge/TefRequestScoped.java | 8 ++ .../TypeListenerForDataInjection.java | 8 ++ .../InjectDataGuiceMembersInjectorTest.java | 82 +++++++++++++++---- 6 files changed, 100 insertions(+), 24 deletions(-) rename tef-impl/src/main/java/flipkart/tef/guicebridge/{SubclassesOfMatcher.java => SubclassOrAnnotationMatcher.java} (61%) diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java index fa06d77..6c5cae5 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/GuiceBridgeModule.java @@ -4,6 +4,10 @@ import com.google.inject.Injector; import com.google.inject.Provides; +/** + * Guice module to setup common infra for the bridge to work + * Date: 1/06/22 + */ public class GuiceBridgeModule extends AbstractModule { private final TefGuiceScope scope; diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassOrAnnotationMatcher.java similarity index 61% rename from tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java rename to tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassOrAnnotationMatcher.java index 858abb5..c17b01b 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassesOfMatcher.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/SubclassOrAnnotationMatcher.java @@ -1,5 +1,6 @@ package flipkart.tef.guicebridge; +import com.google.inject.TypeLiteral; import com.google.inject.matcher.AbstractMatcher; import java.io.Serializable; @@ -8,27 +9,28 @@ /** * Guice matcher for matching subclasses using TypeLiteral. - * + *

* Date: 31/05/22 */ -public class SubclassesOfMatcher extends AbstractMatcher implements Serializable { +public class SubclassOrAnnotationMatcher extends AbstractMatcher> implements Serializable { /* Had to re-implement this class instead of using `Matchers.ofSubclass` since it was not based on TypeLiterals. */ private final Class superclass; - public SubclassesOfMatcher(Class superclass) { + public SubclassOrAnnotationMatcher(Class superclass) { this.superclass = checkNotNull(superclass, "superclass"); } @Override - public boolean matches(Object subclass) { - return superclass.isAssignableFrom(subclass.getClass()); + public boolean matches(TypeLiteral subclass) { + return subclass.getRawType().isAnnotationPresent(TefRequestScoped.class) + || superclass.isAssignableFrom(subclass.getRawType()); } @Override public boolean equals(Object other) { - return other instanceof SubclassesOfMatcher && ((SubclassesOfMatcher) other).superclass.equals(superclass); + return other instanceof SubclassOrAnnotationMatcher && ((SubclassOrAnnotationMatcher) other).superclass.equals(superclass); } @Override diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java index 01b68d9..6ddb3e8 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java @@ -6,6 +6,14 @@ import com.google.inject.Scope; import flipkart.tef.execution.InjectableValueProvider; +/** + * Custom guice scope (request-scoped) that injects an instance of + * + * @see InjectableValueProvider + * from reuquest (thread-local). Other injections are passed over to creator. + *

+ * Date: 1/06/22 + */ public class TefGuiceScope implements Scope, AutoCloseable { private final ThreadLocal threadLocal; diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java index 0344fde..a8491a0 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TefRequestScoped.java @@ -8,6 +8,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Scope annotation for Guice to Tef bridge + * This annotation solves 2 use-cases + *

+ * 1. Bind the custom scope `TefGuiceScope` + * 2. Marker annotation to be used on the classes where @InjectData needs to be powered by guice + * Date: 1/06/22 + */ @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @ScopeAnnotation diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java index 0bb6cec..723300f 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/TypeListenerForDataInjection.java @@ -24,6 +24,14 @@ public void hear(TypeLiteral typeLiteral, TypeEncounter typeEncounter) for (Field field : fields) { if(field.isAnnotationPresent(InjectData.class)) { InjectData injectable = field.getAnnotation(InjectData.class); + + /* + Instance of `InjectDataGuiceMembersInjector` is fetched from provider instead of creating via new + to let guice provide a handle to Injector inside `InjectDataGuiceMembersInjector`. + + That comes in handy to fetch the requestScopedBinding that is used to + inject the instance of `InjectableValueProvider` + */ InjectDataGuiceMembersInjector membersInjector = typeEncounter.getProvider(InjectDataGuiceMembersInjector.class).get(); membersInjector.setField(field); membersInjector.setInjectionName(injectable.name()); diff --git a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java index fddc2e1..6918749 100644 --- a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java +++ b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java @@ -2,7 +2,6 @@ import com.google.inject.AbstractModule; import com.google.inject.Guice; -import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.ProvisionException; import com.google.inject.matcher.Matchers; @@ -18,10 +17,10 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.Callable; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class InjectDataGuiceMembersInjectorTest { @@ -86,7 +85,7 @@ public Object getValueToInject(Class fieldType, String name) throws TefExecut } }; try { - SimpleInterface result = rootInjector.getInstance(SampleCallable.class).call(); + rootInjector.getInstance(SimpleInterface.class); Assert.fail("Injection should have failed"); } catch (ProvisionException e) { assertTrue(e.getCause() instanceof IllegalStateException); @@ -134,6 +133,51 @@ public Object getValueToInject(Class fieldType, String name) throws TefExecut } } + + @Test + public void testSubTypeMatcher() { + + Injector rootInjector = Guice.createInjector(new GuiceBridgeModule(), new AbstractModule() { + @Override + protected void configure() { + bindListener(new SubclassOrAnnotationMatcher(Serializable.class), new TypeListenerForDataInjection()); + } + }); + + + Long threadId = Thread.currentThread().getId(); + try (TefGuiceScope scope = rootInjector.getInstance(TefGuiceScope.class)) { + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new SimpleData())); + dataContext.put(new DataAdapterResult(threadId)); + InjectableValueProvider valueProvider = new InjectableValueProvider() { + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + return dataContext.get(new DataAdapterKey<>(name, fieldType)); + } + }; + + scope.open(valueProvider); + + // Implementations of Serializable + SimpleInterface2 result = rootInjector.getInstance(SimpleInterface2.class); + assertNotNull("Data injection failed", result.simpleData); + assertEquals(threadId, result.threadId); + + // Annotated with TefRequestScope + SimpleInterface3 result3 = rootInjector.getInstance(SimpleInterface3.class); + assertNotNull("Data injection failed", result3.simpleData); + assertEquals(threadId, result3.threadId); + + // Vanilla class + SimpleInterface result1 = rootInjector.getInstance(SimpleInterface.class); + assertNull("Data injection should have failed", result1.simpleData); + assertNull("Data injection should have failed", result1.threadId); + } + } + + static class TesterThread implements Runnable { Injector rootInjector; @@ -158,32 +202,34 @@ public Object getValueToInject(Class fieldType, String name) throws TefExecut }; scope.open(valueProvider); - SimpleInterface result = rootInjector.getInstance(SampleCallable.class).call(); + SimpleInterface result = rootInjector.getInstance(SimpleInterface.class); assertNotNull("Data injection failed", result.simpleData); assertEquals(threadId, result.threadId); } } } - @TefRequestScoped - static class SampleCallable implements Callable { - private SimpleInterface simpleInterface; + static class SimpleData { + } - @Inject - public SampleCallable(SimpleInterface simpleInterface) { - this.simpleInterface = simpleInterface; - } - @Override - public SimpleInterface call() - { - return simpleInterface; - } + static class SimpleInterface { + @InjectData + SimpleData simpleData; + @InjectData + Long threadId; } - static class SimpleData{} - static class SimpleInterface implements Serializable { + static class SimpleInterface2 implements Serializable { + @InjectData + SimpleData simpleData; + @InjectData + Long threadId; + } + + @TefRequestScoped + static class SimpleInterface3 { @InjectData SimpleData simpleData; @InjectData From c8152125cd4c0914af721405f6f4d05822ad7085 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Thu, 2 Jun 2022 17:13:09 +0530 Subject: [PATCH 6/7] Fix for private members --- .../guicebridge/InjectDataGuiceMembersInjector.java | 1 + .../InjectDataGuiceMembersInjectorTest.java | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java index b81ceda..5302ca9 100644 --- a/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java +++ b/tef-impl/src/main/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjector.java @@ -26,6 +26,7 @@ public class InjectDataGuiceMembersInjector implements MembersInjector { public void setField(Field field) { this.field = field; + this.field.setAccessible(true); } public void setInjectionName(String injectionName) { diff --git a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java index 6918749..ec630d9 100644 --- a/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java +++ b/tef-impl/src/test/java/flipkart/tef/guicebridge/InjectDataGuiceMembersInjectorTest.java @@ -215,24 +215,24 @@ static class SimpleData { static class SimpleInterface { @InjectData - SimpleData simpleData; + private SimpleData simpleData; @InjectData - Long threadId; + private Long threadId; } static class SimpleInterface2 implements Serializable { @InjectData - SimpleData simpleData; + private SimpleData simpleData; @InjectData - Long threadId; + private Long threadId; } @TefRequestScoped static class SimpleInterface3 { @InjectData - SimpleData simpleData; + private SimpleData simpleData; @InjectData - Long threadId; + private Long threadId; } } \ No newline at end of file From 67b88420edf0a9fdfb4e53bde415cd52a6c2efba Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Tue, 7 Jun 2022 14:39:27 +0530 Subject: [PATCH 7/7] Release version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a172462..1846cc7 100644 --- a/pom.xml +++ b/pom.xml @@ -107,7 +107,7 @@ - 0.1.0-SNAPSHOT + 0.1.0 19.0 4.2.3