Util.java
package com.cloudforgeci.api.core;
import com.cloudforge.core.interfaces.FrameworkRules;
import com.cloudforgeci.api.core.rules.FrameworkLoader;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
class Util {
private static final Logger LOG = Logger.getLogger(Util.class.getName());
private static ObjectMapper getMapper() {
return new ObjectMapper();
}
/**
* Creates a DeploymentContext from the 'cfc' context object.
*
* <p>This is the entry point for context creation and framework config merging.
* (Renamed from {@code extractDeploymentContext} for clarity.)</p>
*
* @param cfc the raw context object (Map, JSON string, or POJO)
* @return a fully constructed DeploymentContext with merged framework config
*/
public static DeploymentContext createDeploymentContext(Object cfc) {
Map<String, Object> map = convertToContext(cfc);
// Merge framework-required configuration before constructing DeploymentContext
map = mergeFrameworkConfiguration(map);
return new DeploymentContext(map);
}
/**
* @deprecated Use {@link #createDeploymentContext(Object)} instead.
*/
@Deprecated
public static DeploymentContext extractDeploymentContext(Object cfc) {
return createDeploymentContext(cfc);
}
/**
* Merge compliance framework configuration requirements into the deployment context.
*
* <p><b>Precedence order:</b></p>
* <ol>
* <li>User-provided explicit configuration (cdk.json)</li>
* <li>Framework-required configuration (from getRequiredConfiguration())</li>
* <li>Security profile defaults (applied later in SecurityProfileConfiguration)</li>
* </ol>
*
* @param userConfig user-provided configuration map
* @return merged configuration map with framework requirements
*/
private static Map<String, Object> mergeFrameworkConfiguration(Map<String, Object> userConfig) {
// Get enabled frameworks from user config. convertToContext(...) may legitimately
// materialize this as a List (e.g. from a raw JSON array, "complianceFrameworks":
// ["soc2", "hipaa"]) rather than the comma-joined string form the rest of this pipeline
// expects (ComplianceFrameworkType.toCommaSeparated's own canonical shape) — a blind
// (String) cast throws ClassCastException on that otherwise-valid input, before
// DeploymentConfig.fromMap(...) ever gets a chance to normalize it.
Object frameworksValue = userConfig.get("complianceFrameworks");
String frameworksStr = frameworksValue instanceof List<?> list
? list.stream().map(String::valueOf).collect(java.util.stream.Collectors.joining(","))
: (String) frameworksValue;
if (frameworksStr == null || frameworksStr.trim().isEmpty()) {
return userConfig; // No frameworks enabled, return as-is
}
// Parse enabled framework IDs
String[] frameworkIds = frameworksStr.split(",");
// Discover all available frameworks
List<FrameworkRules<SystemContext>> allFrameworks = FrameworkLoader.discover();
// Collect configuration from enabled frameworks
Map<String, Object> frameworkConfig = new HashMap<>();
for (FrameworkRules<SystemContext> framework : allFrameworks) {
String frameworkId = framework.frameworkId();
// Check if this framework is enabled (case-insensitive match)
boolean isEnabled = false;
for (String enabledId : frameworkIds) {
if (enabledId.trim().equalsIgnoreCase(frameworkId)) {
isEnabled = true;
break;
}
}
if (isEnabled) {
Map<String, Object> required = framework.getRequiredConfiguration();
if (required != null && !required.isEmpty()) {
LOG.info("Applying configuration from " + framework.displayName() +
" (" + frameworkId + "): " + required);
// Merge framework config (later frameworks override earlier ones if conflicts)
frameworkConfig.putAll(required);
}
}
}
// Merge: user config takes precedence over framework config
Map<String, Object> merged = new HashMap<>(frameworkConfig);
merged.putAll(userConfig); // User values override framework values
// Log what was applied
if (!frameworkConfig.isEmpty()) {
frameworkConfig.forEach((key, frameworkValue) -> {
Object userValue = userConfig.get(key);
if (userValue != null && !userValue.equals(frameworkValue)) {
LOG.info(" " + key + ": Framework default (" + frameworkValue +
") overridden by user config (" + userValue + ")");
} else if (userValue == null) {
LOG.info(" " + key + ": Applying framework default (" + frameworkValue + ")");
}
});
}
return merged;
}
@SuppressWarnings("unchecked")
private static Map<String, Object> convertToContext(Object obj) {
if (obj == null) return java.util.Collections.emptyMap();
if (obj instanceof Map<?, ?> m) {
Map<String, Object> out = new java.util.HashMap<>();
m.forEach((k, v) -> out.put(String.valueOf(k), v));
return out;
}
if (obj instanceof String s) {
String json = s.trim();
if (json.isEmpty()) return java.util.Collections.emptyMap();
// A leading '@' means "read the JSON from this file" instead of treating the string
// itself as JSON (matches the convention `cdk synth --context cfc=@file.json`
// callers expect, e.g. comprehensive-synth-test.sh) -- resolved relative to the
// JVM's working directory, same as where `cdk synth` itself was invoked from.
if (json.startsWith("@")) {
java.nio.file.Path path = java.nio.file.Path.of(json.substring(1));
try {
json = java.nio.file.Files.readString(path).trim();
} catch (java.io.IOException e) {
throw new RuntimeException("Failed to read context file: " + path, e);
}
}
try {
return getMapper().readValue(json, new TypeReference<Map<String, Object>>() {});
} catch (Exception e) {
throw new RuntimeException("Failed to parse context JSON: " + json, e);
}
}
return getMapper().convertValue(obj, new TypeReference<Map<String, Object>>() {});
}
}