diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java index 3928e58..a8a81fe 100644 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java @@ -24,6 +24,7 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.Dictionary; +import java.util.LinkedHashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -37,8 +38,8 @@ import org.apache.sling.provisioning.model.Section; import org.jetbrains.annotations.Nullable; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.MapType; import io.wcm.devops.conga.plugins.sling.postprocessor.JsonOsgiConfigPostProcessor; @@ -48,9 +49,7 @@ public final class JsonOsgiConfigUtil { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final TypeReference> MAP_TYPE_REFERENCE = new TypeReference>() { - // default implementation - }; + private static final MapType MAP_TYPE = OBJECT_MAPPER.getTypeFactory().constructMapType(Map.class, String.class, Object.class); private static final Pattern KEY_PATTERN_CONFIGURATIONS = Pattern.compile("^configurations(:(.*))?$"); private static final Pattern KEY_PATTERN_REPOINIT = Pattern.compile("^repoinit(:(.*))?$"); @@ -68,7 +67,29 @@ private JsonOsgiConfigUtil() { */ static Map readToMap(File file) throws IOException { String jsonString = FileUtils.readFileToString(file, StandardCharsets.UTF_8); - return OBJECT_MAPPER.readValue(jsonString, MAP_TYPE_REFERENCE); + Map result = OBJECT_MAPPER.readValue(jsonString, MAP_TYPE); + return convertListsToArrays(result); + } + + /** + * Jackson converts arrays in JSON to lists. We want to keep them represented as arrays for conversion + * to OSGi configuration, so we convert them recursively back to Object[] arrays. + */ + @SuppressWarnings("unchecked") + private static Map convertListsToArrays(Map map) { + Map result = new LinkedHashMap<>(); + for (Map.Entry entry : map.entrySet()) { + String key = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Collection) { + value = ((Collection)value).toArray(); + } + else if (value instanceof Map) { + value = convertListsToArrays((Map)value); + } + result.put(key, value); + } + return result; } /** @@ -108,9 +129,9 @@ private static void processEntry(Feature feature, String key, Object value) thro else { Matcher repoinitKeyMatcher = KEY_PATTERN_REPOINIT.matcher(key); if (repoinitKeyMatcher.matches()) { - if (value instanceof Collection) { + if (value.getClass().isArray()) { String[] runModes = toRunModes(repoinitKeyMatcher.group(RUNMODES_INDEX)); - processRepoInit(feature, runModes, (Collection)value); + processRepoInit(feature, runModes, (Object[])value); } else { throw new IOException("Unexpected data for key " + key + ": " + value.getClass().getName()); @@ -155,7 +176,7 @@ private static void processOsgiConfiguration(Feature feature, String[] runModes, /** * Convert repoinit statements to Provisioning model additional sections with associated run modes. */ - private static void processRepoInit(Feature feature, String[] runModes, Collection repoinits) { + private static void processRepoInit(Feature feature, String[] runModes, Object[] repoinits) { Section section = new Section(ProvisioningUtil.REPOINIT_SECTION); feature.getAdditionalSections().add(section); if (runModes != null) { diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java index 19a1734..089c4af 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Dictionary; -import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.felix.cm.json.io.Configurations; @@ -72,7 +71,9 @@ void testJsonFile() throws Exception { // validate generated configs Dictionary config = readConfig("my.pid.cfg.json"); assertEquals("value1", config.get("stringProperty")); - assertEquals(List.of("v1", "v2", "v3"), config.get("stringArrayProperty")); + assertArrayEquals(new String[] { + "v1", "v2", "v3" + }, (String[])config.get("stringArrayProperty")); assertEquals(true, config.get("booleanProperty")); assertEquals(999999999999L, config.get("longProperty")); diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java index 68ca53b..abc57c8 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java @@ -19,11 +19,11 @@ */ package io.wcm.devops.conga.plugins.sling.util; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.File; import java.io.IOException; -import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; @@ -31,9 +31,17 @@ class JsonOsgiConfigUtilTest { @Test + @SuppressWarnings("unchecked") void testReadToMap() throws IOException { Map content = JsonOsgiConfigUtil.readToMap(new File("src/test/resources/osgi-config-json/sample.osgiconfig.json")); - assertEquals(List.of("create service user mode1"), content.get("repoinit:mode1")); + assertArrayEquals(new Object[] { "create service user mode1" }, (Object[])content.get("repoinit:mode1")); + + // assert JSON arrays are represented as array in map instead of list + Map configurations = (Map)content.get("configurations"); + Map my_pid = (Map)configurations.get("my.pid"); + Object stringArrayProperty = my_pid.get("stringArrayProperty"); + assertTrue(stringArrayProperty.getClass().isArray()); + assertArrayEquals(new Object[] { "v1", "v2", "v3" }, (Object[])stringArrayProperty); } }