ContextInjector.java
package com.cloudforgeci.api.core.annotation;
import com.cloudforge.core.annotation.DeploymentContext;
import com.cloudforge.core.annotation.SystemContext;
import com.cloudforge.core.annotation.SecurityProfileConfiguration;
import com.cloudforge.core.enums.TopologyType;
import com.cloudforge.core.enums.RuntimeType;
import com.cloudforge.core.enums.SecurityProfile;
import software.constructs.Construct;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Standalone utility for injecting context values into annotated fields.
*
* <p>This allows any class (not just BaseFactory subclasses) to use the
* {@code @DeploymentContext}, {@code @SystemContext}, and
* {@code @SecurityProfileConfiguration} annotations for dependency injection.</p>
*
* <h2>Slot Auto-Extraction</h2>
* <p>When a {@code @SystemContext} field references a Slot, the injector automatically extracts
* the value from the Slot. No need for manual {@code .get().orElseThrow()} calls!</p>
*
* <p>Usage examples:</p>
* <pre>{@code
* public class MyCustomClass {
* // Simple context value injection
* @DeploymentContext("region")
* private String region;
*
* // Direct field injection (non-Slot)
* @SystemContext("security")
* private SecurityProfile security;
*
* // Automatic Slot extraction - no .get().orElseThrow() needed!
* @SystemContext("alb")
* private IApplicationLoadBalancer alb; // Auto-extracted from ctx.alb Slot
*
* @SystemContext("vpc")
* private Vpc vpc; // Auto-extracted from ctx.vpc Slot
*
* public MyCustomClass(Construct scope) {
* // Inject all annotated fields
* ContextInjector.inject(this, scope);
* }
* }
* }</pre>
*
* <h2>Error Handling</h2>
* <p>If a Slot is empty when accessed, a clear error message indicates which resource
* is missing and reminds you to create it first.</p>
*/
public final class ContextInjector {
private ContextInjector() {
// Utility class - prevent instantiation
}
/**
* Inject context values into all annotated fields of the given object.
*
* @param target The object whose fields should be injected
* @param scope The CDK construct scope to retrieve context from
*/
public static void inject(Object target, Construct scope) {
if (target == null) {
throw new IllegalArgumentException("Injection target cannot be null");
}
if (scope == null) {
throw new IllegalArgumentException("Construct scope cannot be null");
}
com.cloudforgeci.api.core.SystemContext systemContext =
com.cloudforgeci.api.core.SystemContext.of(scope);
com.cloudforgeci.api.core.DeploymentContext deploymentContext =
com.cloudforgeci.api.core.DeploymentContext.from(scope);
inject(target, systemContext, deploymentContext);
}
/**
* Inject context values into all annotated fields of the given object.
*
* @param target The object whose fields should be injected
* @param systemContext The SystemContext to inject from
* @param deploymentContext The DeploymentContext to inject from
*/
public static void inject(Object target,
com.cloudforgeci.api.core.SystemContext systemContext,
com.cloudforgeci.api.core.DeploymentContext deploymentContext) {
if (target == null) {
throw new IllegalArgumentException("Injection target cannot be null");
}
if (systemContext == null) {
throw new IllegalArgumentException("SystemContext cannot be null");
}
if (deploymentContext == null) {
throw new IllegalArgumentException("DeploymentContext cannot be null");
}
// Get security profile configuration
com.cloudforgeci.api.interfaces.SecurityProfileConfiguration securityConfig =
getSecurityProfileConfiguration(systemContext.security, deploymentContext);
// Inject fields from all classes in the hierarchy
Class<?> clazz = target.getClass();
while (clazz != null && clazz != Object.class) {
for (Field field : clazz.getDeclaredFields()) {
try {
// Handle @DeploymentContext annotation
DeploymentContext deploymentContextAnnotation =
field.getAnnotation(DeploymentContext.class);
if (deploymentContextAnnotation != null && !deploymentContextAnnotation.value().isEmpty()) {
Object value = extractValueFromContext(deploymentContext, deploymentContextAnnotation.value());
setFieldValue(target, field, value);
continue;
}
// Handle @SystemContext annotation
SystemContext systemContextAnnotation =
field.getAnnotation(SystemContext.class);
if (systemContextAnnotation != null && !systemContextAnnotation.value().isEmpty()) {
Object value = extractValueFromContext(systemContext, systemContextAnnotation.value());
setFieldValue(target, field, value);
continue;
}
// Handle @SecurityProfileConfiguration annotation
SecurityProfileConfiguration securityConfigAnnotation =
field.getAnnotation(SecurityProfileConfiguration.class);
if (securityConfigAnnotation != null && !securityConfigAnnotation.value().isEmpty()) {
Object value = extractValueFromContext(securityConfig, securityConfigAnnotation.value());
setFieldValue(target, field, value);
}
} catch (Exception e) {
// Log and continue - don't fail the injection
System.err.println("Failed to inject field " + field.getName() + ": " + e.getMessage());
}
}
clazz = clazz.getSuperclass();
}
}
/**
* Extract a value from a context object by accessing its field or calling its getter method.
* Automatically extracts values from Slot objects.
*/
private static Object extractValueFromContext(Object contextObject, String propertyName) throws Exception {
if (contextObject == null) {
throw new IllegalArgumentException("Context object cannot be null");
}
if (propertyName == null || propertyName.isEmpty()) {
throw new IllegalArgumentException("Property name cannot be null or empty");
}
Object value = null;
// Try to access as a public field first (e.g., SystemContext.stackName)
try {
Field contextField = contextObject.getClass().getField(propertyName);
value = contextField.get(contextObject);
} catch (NoSuchFieldException e) {
// Not a field, try methods instead
}
// If field access didn't work, try methods
if (value == null) {
// Try standard getter method (e.g., "region" -> "region()")
Method method = findMethod(contextObject.getClass(), propertyName);
if (method != null) {
value = method.invoke(contextObject);
}
// Try "get" prefix (e.g., "wafEnabled" -> "getWafEnabled()")
if (value == null) {
method = findMethod(contextObject.getClass(), "get" + capitalize(propertyName));
if (method != null) {
value = method.invoke(contextObject);
}
}
// Try "is" prefix for boolean (e.g., "wafEnabled" -> "isWafEnabled()")
if (value == null) {
method = findMethod(contextObject.getClass(), "is" + capitalize(propertyName));
if (method != null) {
value = method.invoke(contextObject);
}
}
}
if (value == null) {
throw new NoSuchMethodException("No field or getter found for property: " + propertyName);
}
// Auto-extract from Slot if the value is a Slot object
if (value instanceof com.cloudforgeci.api.core.Slot<?> slot) {
return slot.get().orElseThrow(() ->
new IllegalStateException("Required slot '" + propertyName + "' is not set. " +
"Ensure the corresponding factory creates this resource before injection.")
);
}
return value;
}
/**
* Find a method by name in a class or its superclasses.
*/
private static Method findMethod(Class<?> clazz, String methodName) {
try {
return clazz.getMethod(methodName);
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Set the value of a field using reflection.
*/
private static void setFieldValue(Object target, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
field.set(target, value);
}
/**
* Capitalize the first letter of a string.
*/
private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1);
}
/**
* Get the appropriate security profile configuration based on the security profile.
*/
private static com.cloudforgeci.api.interfaces.SecurityProfileConfiguration getSecurityProfileConfiguration(
SecurityProfile securityProfile,
com.cloudforgeci.api.core.DeploymentContext deploymentContext) {
return switch (securityProfile) {
case DEV -> new com.cloudforgeci.api.core.security.DevSecurityProfileConfiguration(deploymentContext);
case STAGING -> new com.cloudforgeci.api.core.security.StagingSecurityProfileConfiguration(deploymentContext);
case PRODUCTION -> new com.cloudforgeci.api.core.security.ProductionSecurityProfileConfiguration(deploymentContext);
};
}
}