diff --git a/java/pom.xml b/java/pom.xml index ce37ff5b..f6d6ff71 100644 --- a/java/pom.xml +++ b/java/pom.xml @@ -185,17 +185,19 @@ jakarta.inject-api 2.0.1 - + + + - com.squareup.dagger + com.google.dagger dagger - 1.2.5 + 2.51.1 compile - com.squareup.dagger + com.google.dagger dagger-compiler - 1.2.5 + 2.51.1 compile diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/DIContainer.java b/java/src/main/java/com/google/appengine/tools/pipeline/DIContainer.java new file mode 100644 index 00000000..9b9722bd --- /dev/null +++ b/java/src/main/java/com/google/appengine/tools/pipeline/DIContainer.java @@ -0,0 +1,6 @@ +package com.google.appengine.tools.pipeline; + +public interface DIContainer { + void inject(T instance); + +} diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/DefaultContainer.java b/java/src/main/java/com/google/appengine/tools/pipeline/DefaultContainer.java new file mode 100644 index 00000000..8d2233c9 --- /dev/null +++ b/java/src/main/java/com/google/appengine/tools/pipeline/DefaultContainer.java @@ -0,0 +1,18 @@ +package com.google.appengine.tools.pipeline; + +import com.google.appengine.tools.pipeline.impl.PipelineManager; +import dagger.Component; + +import javax.inject.Singleton; + +@Singleton +@Component( + modules = { + DefaultDIModule.class, + } +) +public interface DefaultContainer { + + void injects(PipelineManager pipelineManager); + +} diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/DefaultDIModule.java b/java/src/main/java/com/google/appengine/tools/pipeline/DefaultDIModule.java index 6de65f97..b5d4f022 100644 --- a/java/src/main/java/com/google/appengine/tools/pipeline/DefaultDIModule.java +++ b/java/src/main/java/com/google/appengine/tools/pipeline/DefaultDIModule.java @@ -4,7 +4,6 @@ import com.google.appengine.api.utils.SystemProperty; import com.google.appengine.tools.pipeline.impl.PipelineManager; import com.google.appengine.tools.pipeline.impl.backend.*; -import com.google.appengine.tools.pipeline.impl.servlets.PipelineServlet; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.datastore.DatastoreOptions; import dagger.Module; @@ -14,13 +13,7 @@ import javax.inject.Singleton; //TODO: split internals v stuff that can be re-used by others -@Module( - injects = { //alphabetical order - PipelineServlet.class, - }, - library = true, - complete = false -) +@Module public class DefaultDIModule { @Provides @Singleton @@ -33,6 +26,11 @@ AppEngineBackEnd.Options appEngineBackEndOptions() { .build(); } + @Provides @Singleton + PipelineBackEnd pipelineBackEnd(AppEngineBackEnd appEngineBackEnd) { + return appEngineBackEnd; + } + @Provides @Singleton PipelineBackEnd.Options backendOptions(AppEngineBackEnd.Options options) { return options; diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/Injectable.java b/java/src/main/java/com/google/appengine/tools/pipeline/Injectable.java index 2edd23e8..8d843442 100644 --- a/java/src/main/java/com/google/appengine/tools/pipeline/Injectable.java +++ b/java/src/main/java/com/google/appengine/tools/pipeline/Injectable.java @@ -6,7 +6,7 @@ /** * equivalent of Spring component, in a sense * - * FQN of Dagger 1 module class that should be used when filling this classes dependencies, if any. + * FQN of Dagger 2 module class that should be used when filling this classes dependencies, if any. * * * Usage: diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/impl/PipelineManager.java b/java/src/main/java/com/google/appengine/tools/pipeline/impl/PipelineManager.java index 394e7056..8a572be7 100755 --- a/java/src/main/java/com/google/appengine/tools/pipeline/impl/PipelineManager.java +++ b/java/src/main/java/com/google/appengine/tools/pipeline/impl/PipelineManager.java @@ -778,7 +778,7 @@ private void inject(Job job) { Optional injectable = Optional.ofNullable(job.getClass().getAnnotation(Injectable.class)); if (injectable.isPresent()) { - DIUtil.inject(injectable.get().value().getName(), job); + DIUtil.inject(injectable.get().value(), job); } } diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/impl/servlets/PipelineServlet.java b/java/src/main/java/com/google/appengine/tools/pipeline/impl/servlets/PipelineServlet.java index 43d21b2c..bbc45f77 100755 --- a/java/src/main/java/com/google/appengine/tools/pipeline/impl/servlets/PipelineServlet.java +++ b/java/src/main/java/com/google/appengine/tools/pipeline/impl/servlets/PipelineServlet.java @@ -14,6 +14,7 @@ package com.google.appengine.tools.pipeline.impl.servlets; +import com.google.appengine.tools.pipeline.DaggerDefaultContainer; import com.google.appengine.tools.pipeline.DefaultDIModule; import com.google.appengine.tools.pipeline.PipelineOrchestrator; import com.google.appengine.tools.pipeline.PipelineRunner; @@ -112,7 +113,7 @@ public void init() throws ServletException { // TODO: fix this? may have second copy IF user overrides; OK? if (pipelineManager == null) { - DIUtil.inject(DefaultDIModule.class.getName(), this); + DIUtil.inject(DaggerDefaultContainer.class, this); } //TODO: move these to DI? diff --git a/java/src/main/java/com/google/appengine/tools/pipeline/impl/util/DIUtil.java b/java/src/main/java/com/google/appengine/tools/pipeline/impl/util/DIUtil.java index 8ca6219f..51b55177 100644 --- a/java/src/main/java/com/google/appengine/tools/pipeline/impl/util/DIUtil.java +++ b/java/src/main/java/com/google/appengine/tools/pipeline/impl/util/DIUtil.java @@ -1,41 +1,52 @@ package com.google.appengine.tools.pipeline.impl.util; +/** + * Copyright Worklytics, Co. 2024. + */ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; -import dagger.ObjectGraph; + import lombok.NonNull; import lombok.extern.java.Log; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** - * Helper to get an ObjectGraph out of a Dagger Module. + * Helper to get a component instance out of Dagger, support runtime injection of classes. *

- * This is mostly intended to be used for jobs, thus Default Module is linked somehow to AsyncModule. * */ - @Log public class DIUtil { private static final Object lock = new Object(); - private static final Map objectGraphCache = new ConcurrentHashMap<>(1); + private static final Map, Object> componentCache = new ConcurrentHashMap<>(1); - private static final Map overriddenObjectGraphCache = new ConcurrentHashMap<>(1); + private static final Map, Object> overriddenComponentCache = new ConcurrentHashMap<>(1); // just for logging purposes private static boolean overridden = false; /** * Injects and Injectable instance through Reflection + * @param componentClass component class (Dagger-generated) * @param injectable class to inject */ - public static void inject(String moduleClassFQN, Object injectable) { - ObjectGraph objectGraph = getFromModuleClass(moduleClassFQN); - objectGraph.inject(injectable); + public static void inject(Class componentClass, Object injectable) { + Object objectGraph = getFromComponentClass(componentClass); + + try { + Method injectMethod = objectGraph.getClass().getMethod("inject", injectable.getClass()); + injectMethod.setAccessible(true); //avoid 'volatile' thing + injectMethod.invoke(objectGraph, injectable); + } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { + throw new RuntimeException("Couldn't inject object " + e.getMessage(), e); + } } /** @@ -47,47 +58,41 @@ public static void inject(String moduleClassFQN, Object injectable) { * - Module has a static instance of the graph it builds and provided by method of signature * public static ObjectGraph getObjectGraph() * - * @param moduleFQNClassName module fully qualified name + * @param componentClass module fully qualified name * @return the object graph for that module * @throws RuntimeException if the class is not a Dagger Module or doesn't meet the requirements */ - public static ObjectGraph getFromModuleClass(@NonNull String moduleFQNClassName) { + public static Object getFromComponentClass(@NonNull Class componentClass) { synchronized (lock) { if (overridden) { - return overriddenObjectGraphCache.getOrDefault(moduleFQNClassName, objectGraphCache.get(moduleFQNClassName)); + return overriddenComponentCache.getOrDefault(componentClass, componentCache.get(componentClass)); } try { - Class moduleClass = Class.forName(moduleFQNClassName); - Preconditions.checkArgument(moduleClass.isAnnotationPresent(dagger.Module.class), "Class is not a Dagger Module: " + moduleFQNClassName); - - ObjectGraph objectGraph = ObjectGraph.create(moduleClass.newInstance()); - objectGraphCache.put(moduleFQNClassName, objectGraph); - return objectGraph; - } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) { + Object component = componentClass.getMethod("create").invoke(null); + componentCache.put(componentClass, component); + return component; + } catch (IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { throw new RuntimeException("Couldn't get an object graph " + e.getMessage(), e); } } } - /** - * A test helper to directly override the default module with the test's graph - * @param graph the object graph to override for default module - */ - @VisibleForTesting - public static void overrideGraphForTests(String key, ObjectGraph graph) { - synchronized (lock) { - overriddenObjectGraphCache.put(key, graph); - overridden = true; - } - } + + @VisibleForTesting + public static void overrideComponentInstanceForTests(Class clazz, Object componentInstance) { + synchronized (lock) { + overriddenComponentCache.put(clazz, componentInstance); + overridden = true; + } + } /** - * Resets the objectGraphCache to its original state (intended to be used on tests' teardown) + * Resets the componentCache to its original state (intended to be used on tests' teardown) */ @VisibleForTesting - public static void resetGraph() { + public static void resetComponents() { synchronized (lock) { - overriddenObjectGraphCache.clear(); + overriddenComponentCache.clear(); overridden = false; log.fine("Reset overridden DI Module"); } diff --git a/java/src/test/java/com/google/appengine/tools/pipeline/DITest.java b/java/src/test/java/com/google/appengine/tools/pipeline/DITest.java index 9f220bdd..921eff62 100644 --- a/java/src/test/java/com/google/appengine/tools/pipeline/DITest.java +++ b/java/src/test/java/com/google/appengine/tools/pipeline/DITest.java @@ -2,12 +2,14 @@ import com.google.appengine.tools.pipeline.impl.backend.AppEngineEnvironment; import com.google.appengine.tools.pipeline.impl.util.DIUtil; +import dagger.Component; import dagger.Module; import javax.inject.Inject; // jakarta.inject.Inject not working with dagger yet (maybe in 2.0??) import dagger.Provides; import lombok.NoArgsConstructor; import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,18 +19,20 @@ public class DITest extends PipelineTest { - - @Module( - injects = { - JobWithDependency.class, - }, - includes = { - DefaultDIModule.class, - }, - complete = false, - library = true, - overrides = true + @Component( + modules = { + //DefaultDIModule.class, + DIModule.class, + } ) + public interface Container extends DIContainer { + + //NOTE: have to do this for every class that may need to be injected (eg, Jobs, Tests, etc) + void inject(JobWithDependency job); + } + + + @Module public static class DIModule { @Provides @@ -41,17 +45,22 @@ public String getService() { @Override public String getVersion() { - return "v0.1"; + return "v0.1-test"; } }; } + } - - + @BeforeEach + public void setup() { + Container container = DaggerDITest_Container.create(); + container.inject(this); + DIUtil.overrideComponentInstanceForTests(DaggerDITest_Container.class, container); } + @NoArgsConstructor - @Injectable(DIModule.class) + @Injectable(DaggerDITest_Container.class) public static class JobWithDependency extends Job0 { @Inject @@ -67,7 +76,7 @@ public Value run() throws Exception { public void directInjection() { JobWithDependency job = new JobWithDependency(); - DIUtil.inject(DIModule.class.getName(), job); + DIUtil.inject(DaggerDITest_Container.class, job); assertNotNull(job.appEngineEnvironment); } @@ -76,6 +85,6 @@ public void directInjection() { public void testJobWithDependency(PipelineService pipelineService) { String pipelineId = pipelineService.startNewPipeline(new JobWithDependency()); String value = waitForJobToComplete(pipelineService, pipelineId); - assertEquals("dsaf", value); + assertEquals("v0.1-test", value); } }