Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use inject for non singleton class #1617

Merged
merged 34 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
100b54e
comment to start branch
jorg3lopez Nov 4, 2024
b5ddf86
getFieldsAnnotatedWithInstance helper method
jorg3lopez Nov 25, 2024
c0bd221
todo comment deleted
jorg3lopez Nov 25, 2024
4ddefce
injectIntoField overloaded method
jorg3lopez Nov 25, 2024
ee807ea
injectIntoNonSingleton
jorg3lopez Nov 25, 2024
740f79d
access modifier protected to public
jorg3lopez Nov 25, 2024
006212e
revert access modifier change
jorg3lopez Nov 25, 2024
067eba4
changes:
jorg3lopez Nov 25, 2024
3d9c4f1
Added implementors unit test to ApplicationContextTest
jorg3lopez Nov 26, 2024
19102d8
Merge branch 'main' into use-inject-for-non-singleton-class
luis-pabon-tf Nov 26, 2024
be48aed
Merge remote-tracking branch 'refs/remotes/origin/use-inject-for-non-…
jorg3lopez Nov 26, 2024
875b86b
Minor refactoring on some AppContext injection methods
luis-pabon-tf Nov 26, 2024
4adaa27
Make the skip flag on TestApplicationContext switchable from the test…
luis-pabon-tf Nov 26, 2024
c46e4e1
Merge branch 'main' into use-inject-for-non-singleton-class
luis-pabon-tf Dec 2, 2024
ff4781a
Update ApplicationContext.java
luis-pabon-tf Dec 3, 2024
e5d184d
Update TestApplicationContext.groovy
luis-pabon-tf Dec 3, 2024
083ddb4
Update TestApplicationContext.groovy
luis-pabon-tf Dec 3, 2024
0ac729e
added unit tests to cover new code
jorg3lopez Dec 4, 2024
18c2f48
Update ApplicationContextTest.groovy
luis-pabon-tf Dec 5, 2024
a533d0d
Merge pull request #1639 from CDCgov/application_context_fix_unreacha…
luis-pabon-tf Dec 5, 2024
88d128e
Merge branch 'main' into use-inject-for-non-singleton-class
luis-pabon-tf Dec 5, 2024
2d1864c
Merge branch 'use-inject-for-non-singleton-class' of https://github.c…
luis-pabon-tf Dec 5, 2024
51af9ab
Update ApplicationContext.java
luis-pabon-tf Dec 9, 2024
8894b15
Merge branch 'main' into use-inject-for-non-singleton-class
luis-pabon-tf Dec 9, 2024
0f9bdb4
Merge branch 'main' into use-inject-for-non-singleton-class
jorg3lopez Dec 11, 2024
a1ab899
use return instead of System.err
jorg3lopez Dec 12, 2024
5672dcc
refactoring - dry
jorg3lopez Dec 12, 2024
e9f6e02
thown(NullPointerException) -> noExceptionThrown()
jorg3lopez Dec 12, 2024
ba81073
added comments to indicate changes that will be deleted
jorg3lopez Dec 12, 2024
c3cce16
deleted commented out code
jorg3lopez Dec 12, 2024
dde8378
Merge branch 'main' into use-inject-for-non-singleton-class
jorg3lopez Dec 12, 2024
2e81fd8
Merge branch 'main' into use-inject-for-non-singleton-class
jorg3lopez Dec 12, 2024
f8f6e5b
reinstated comments for test case
jorg3lopez Dec 12, 2024
37a57fa
deleted test changes:
jorg3lopez Dec 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class ApplicationContext {
protected static final Map<String, String> TEST_ENV_VARS = new ConcurrentHashMap<>();
protected static final Set<Object> IMPLEMENTATIONS = new HashSet<>();

protected static boolean skipMissingImplementations = false;

protected ApplicationContext() {}

public static void register(Class<?> clazz, Object implementation) {
Expand All @@ -53,17 +55,39 @@ public static <T> Set<Class<? extends T>> getImplementors(Class<T> interfaze) {
}

public static void injectRegisteredImplementations() {
injectRegisteredImplementations(false);
doInjectRegisteredImplementations();
}

protected static void injectRegisteredImplementations(boolean skipMissingImplementations) {
protected static void doInjectRegisteredImplementations() {
var fields = Reflection.getFieldsAnnotatedWith(Inject.class);

fields.forEach(field -> injectIntoField(field, skipMissingImplementations));
fields.forEach(ApplicationContext::injectIntoField);
}

public static void injectIntoNonSingleton(Object instance) {
var fields = Reflection.getFieldsAnnotatedWithInstance(instance.getClass(), Inject.class);

fields.forEach(field -> injectIntoField(field, instance));
}

private static void injectIntoField(Field field, boolean skipMissingImplementations) {
private static void injectIntoField(Field field, Object instance) {
jorg3lopez marked this conversation as resolved.
Show resolved Hide resolved
var fieldType = field.getType();

Object fieldImplementation = getFieldImplementation(fieldType);
if (fieldImplementation == null) {
return;
}

field.trySetAccessible();
try {
field.set(instance, fieldImplementation);
} catch (IllegalAccessException | IllegalArgumentException exception) {
throw new IllegalArgumentException(
jorg3lopez marked this conversation as resolved.
Show resolved Hide resolved
"unable to inject " + fieldType + " into " + instance.getClass(), exception);
}
}

private static void injectIntoField(Field field) {
var declaringClass = field.getDeclaringClass();

if (!IMPLEMENTATIONS.contains(declaringClass)) {
Expand All @@ -76,29 +100,16 @@ private static void injectIntoField(Field field, boolean skipMissingImplementati
declaringClassesToTry.add(declaringClass);
declaringClassesToTry.addAll(Arrays.asList(declaringClass.getInterfaces()));

Object fieldImplementation = getFieldImplementation(fieldType, skipMissingImplementations);
if (fieldImplementation == null) {
return;
}

Object declaringClassImplementation =
getDeclaringClassImplementation(declaringClassesToTry, skipMissingImplementations);
getDeclaringClassImplementation(declaringClassesToTry);
if (declaringClassImplementation == null) {
return;
}

field.trySetAccessible();

try {
field.set(declaringClassImplementation, fieldImplementation);
} catch (IllegalAccessException | IllegalArgumentException exception) {
throw new IllegalArgumentException(
"Unable to inject " + fieldType + " into " + declaringClass, exception);
}
injectIntoField(field, declaringClassImplementation);
}

private static Object getFieldImplementation(
Class<?> fieldType, boolean skipMissingImplementations) {
private static Object getFieldImplementation(Class<?> fieldType) {
Object fieldImplementation;

try {
Expand All @@ -116,8 +127,7 @@ private static Object getFieldImplementation(
return fieldImplementation;
}

private static Object getDeclaringClassImplementation(
List<Class<?>> declaringClassesToTry, boolean skipMissingImplementations) {
private static Object getDeclaringClassImplementation(List<Class<?>> declaringClassesToTry) {
Object declaringClassImplementation =
declaringClassesToTry.stream()
.map(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@
import static org.reflections.scanners.Scanners.FieldsAnnotated;
import static org.reflections.scanners.Scanners.SubTypes;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Set;
import java.util.stream.Collectors;
import org.reflections.Reflections;

/**
Expand All @@ -27,4 +30,10 @@ public static <T> Set<Class<? extends T>> getImplementors(Class<T> interfaze) {
public static Set<Field> getFieldsAnnotatedWith(Class<?> annotation) {
return REFLECTIONS.get(FieldsAnnotated.with(annotation).as(Field.class));
}

public static Set<Field> getFieldsAnnotatedWithInstance(Class<?> clazz, Class<?> annotation) {
return Arrays.stream(clazz.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(annotation.asSubclass(Annotation.class)))
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package gov.hhs.cdc.trustedintermediary.context

import gov.hhs.cdc.trustedintermediary.wrappers.Logger
import spock.lang.Specification

import javax.inject.Inject
Expand All @@ -8,6 +9,34 @@ import java.nio.file.Paths

class ApplicationContextTest extends Specification {

interface TestingInterface {
void test()
}

class NonSingletonClazz {
@Inject
Logger logger
void test() {}
}

static class DogCow implements TestingInterface {

@Override
void test() {
print("test()")
}
}

static class DogCowTwo implements TestingInterface {

@Override
void test() {
print("testTwo()")
}
}
def DOGCOW = new DogCow()
def DOGCOWTWO = new DogCowTwo()

def setup() {
TestApplicationContext.reset()
}
Expand All @@ -21,6 +50,48 @@ class ApplicationContextTest extends Specification {
result == ApplicationContext.getImplementation(String.class)
}

def "implementors retrieval test"() {
setup:
def dogCow = DOGCOW
def dogCowTwo = DOGCOWTWO
def implementors = new HashSet()
implementors.add(DogCow)
implementors.add(DogCowTwo)

expect:
implementors == ApplicationContext.getImplementors(TestingInterface)
}

def "injectIntoNonSingleton unhappy path"() {
given:
def nonSingletonClass = new NonSingletonClazz()
def object = new Object()
ApplicationContext.register(Logger, object)
when:
ApplicationContext.injectIntoNonSingleton(nonSingletonClass)
then:
thrown(IllegalArgumentException)
}

def "injectIntoNonSingleton unhappy path when fieldImplementation runs into an error"() {
given:
def nonSingletonClass = new NonSingletonClazz()
when:
ApplicationContext.injectIntoNonSingleton(nonSingletonClass)
then:
thrown(IllegalArgumentException)
}

def "injectIntoNonSingleton unhappy path when fieldImplementation is null"() {
given:
def nonSingletonClass = new NonSingletonClazz()
when:
ApplicationContext.skipMissingImplementations = true
ApplicationContext.injectIntoNonSingleton(nonSingletonClass)
then:
noExceptionThrown()
}

def "implementation injection test"() {
given:
def injectedValue = "DogCow"
Expand Down Expand Up @@ -133,6 +204,25 @@ class ApplicationContextTest extends Specification {
Files.deleteIfExists(directoryPath)
}

def "registering an unsupported injection class"() {
given:
def injectedValue = "DogCow"
def injectionInstantiation = new InjectionDeclaringClass()

TestApplicationContext.register(List.class, injectionInstantiation)
// notice above that I'm registering the injectionInstantiation object as a List class.
// injectionInstantiation is of class InjectionDeclaringClass,
// and InjectionDeclaringClass doesn't implement List (it only implements AFieldInterface).
TestApplicationContext.register(String.class, injectedValue)

when:
TestApplicationContext.injectRegisteredImplementations()
injectionInstantiation.getAField()

then:
noExceptionThrown()
}

class InjectionDeclaringClass {
@Inject
private String aField
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ class TestApplicationContext extends ApplicationContext {
}

def static injectRegisteredImplementations() {
injectRegisteredImplementations(true)
skipMissingImplementations = true
ApplicationContext.injectRegisteredImplementations()
}

def static addEnvironmentVariable(String key, String value) {
Expand Down
Loading