Skip to content

Commit

Permalink
Merge pull request #5 from flipkart-incubator/guige-bridge
Browse files Browse the repository at this point in the history
Guice bridge
  • Loading branch information
bageshwar authored Jun 7, 2022
2 parents b3147dd + 67b8842 commit ed3c116
Show file tree
Hide file tree
Showing 11 changed files with 497 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@
</build>

<properties>
<revision>0.0.3-SNAPSHOT</revision>
<revision>0.1.0</revision>
<guava.version>19.0</guava.version>
<guice.version>4.2.3</guice.version>
</properties>
Expand Down
1 change: 0 additions & 1 deletion tef-impl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
<version>4.12</version>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package flipkart.tef.execution;

import com.google.common.reflect.Reflection;
import flipkart.tef.annotations.InjectData;
import flipkart.tef.exception.TefExecutionException;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package flipkart.tef.guicebridge;

import com.google.inject.AbstractModule;
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;

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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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;

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<T> implements MembersInjector<T> {
private Field field;
private String injectionName;
private final Injector injector;

// package-private to let only the type listener create an instance
InjectDataGuiceMembersInjector(Injector injector){
this.injector = injector;
}

public void setField(Field field) {
this.field = field;
this.field.setAccessible(true);
}

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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package flipkart.tef.guicebridge;

import com.google.inject.TypeLiteral;
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.
* <p>
* Date: 31/05/22
*/
public class SubclassOrAnnotationMatcher extends AbstractMatcher<TypeLiteral<?>> 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 SubclassOrAnnotationMatcher(Class<?> superclass) {
this.superclass = checkNotNull(superclass, "superclass");
}

@Override
public boolean matches(TypeLiteral<?> subclass) {
return subclass.getRawType().isAnnotationPresent(TefRequestScoped.class)
|| superclass.isAssignableFrom(subclass.getRawType());
}

@Override
public boolean equals(Object other) {
return other instanceof SubclassOrAnnotationMatcher && ((SubclassOrAnnotationMatcher) 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;
}
56 changes: 56 additions & 0 deletions tef-impl/src/main/java/flipkart/tef/guicebridge/TefGuiceScope.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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;
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.
* <p>
* Date: 1/06/22
*/
public class TefGuiceScope implements Scope, AutoCloseable {

private final ThreadLocal<InjectableValueProvider> threadLocal;

public TefGuiceScope() {
threadLocal = new ThreadLocal<>();
}

@SuppressWarnings("unchecked")
public <T> Provider<T> scope(final Key<T> key, final Provider<T> creator) {
return new Provider<T>() {
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();
}
}

public String toString() {
return String.format("%s[%s]", creator, "TefRequestScoped");
}
};
}

public String toString() {
return "TefRequestScoped";
}

public void open(InjectableValueProvider valueProvider){
Preconditions.checkState(threadLocal.get() == null, "A scoping block is already in progress");
threadLocal.set(valueProvider);
}

@Override
public void close() {
threadLocal.remove();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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;

/**
* Scope annotation for Guice to Tef bridge
* This annotation solves 2 use-cases
* <p>
* 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
public @interface TefRequestScoped {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package flipkart.tef.guicebridge;

import com.google.inject.TypeLiteral;
import com.google.inject.spi.TypeEncounter;
import com.google.inject.spi.TypeListener;
import flipkart.tef.annotations.InjectData;

import java.lang.reflect.Field;

/**
* TypeListener to process instances which are using the @InjectData annotation.
*
* Date: 31/05/22
*/
public class TypeListenerForDataInjection implements TypeListener {

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public <I> void hear(TypeLiteral<I> typeLiteral, TypeEncounter<I> 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);

/*
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());
typeEncounter.register(membersInjector);
}
}
bizlogicClass = bizlogicClass.getSuperclass();
}
}
}
12 changes: 12 additions & 0 deletions tef-impl/src/test/java/flipkart/tef/TestTefContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<String, Object> additionalContext, Injector injector, Consumer<Throwable> exceptionLogger) {
super(additionalContext, injector, exceptionLogger);
}
}
Loading

0 comments on commit ed3c116

Please sign in to comment.