From 17ddd48c9ae12a6efbbb89060367540774c0c198 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 9 Jan 2024 15:27:45 +0100 Subject: [PATCH] ProvisioningOsgiConfigPostProcessor: Write OSGi configurations as .cfg.json files instead of .config files (#6) --- changes.xml | 9 + conga-sling-plugin/pom.xml | 32 +- conga-sling-plugin/src/it/example/pom.xml | 2 +- .../ConfigurationHandler_ConfigAdmin184.java | 731 ------------------ .../plugins/sling/util/OsgiConfigUtil.java | 14 +- .../plugins/sling/util/ProvisioningUtil.java | 2 +- ...ovisioningOsgiConfigPostProcessorTest.java | 32 +- .../src/test/resources/configSample.cfg.json | 5 + parent/pom.xml | 2 +- pom.xml | 4 +- 10 files changed, 72 insertions(+), 761 deletions(-) delete mode 100644 conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ConfigurationHandler_ConfigAdmin184.java create mode 100644 conga-sling-plugin/src/test/resources/configSample.cfg.json diff --git a/changes.xml b/changes.xml index 611239d..4c4239a 100644 --- a/changes.xml +++ b/changes.xml @@ -23,6 +23,15 @@ xsi:schemaLocation="http://maven.apache.org/changes/1.0.0 http://maven.apache.org/plugins/maven-changes-plugin/xsd/changes-1.0.0.xsd"> + + + Add OsgiCfgJsonFileHeader to provide file header support for .cfg.json files. + + + ProvisioningOsgiConfigPostProcessor: Write OSGi configurations as .cfg.json files instead of .config files. + + + Generate run modes for configurations in order as expected by AEM Analyser Plugin. diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index cb1b723..3a9cab8 100644 --- a/conga-sling-plugin/pom.xml +++ b/conga-sling-plugin/pom.xml @@ -25,13 +25,13 @@ io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.parent - 1.3.5-SNAPSHOT + 1.4.0-SNAPSHOT ../parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling - 1.3.5-SNAPSHOT + 1.4.0-SNAPSHOT jar CONGA Sling Plugin @@ -60,7 +60,7 @@ io.wcm.devops.conga io.wcm.devops.conga.generator - 1.16.2 + 1.16.5-SNAPSHOT compile @@ -68,7 +68,6 @@ org.apache.felix org.apache.felix.configadmin - 1.9.26 compile @@ -80,6 +79,31 @@ compile + + org.apache.felix + org.apache.felix.cm.json + 2.0.4 + compile + + + jakarta.json + jakarta.json-api + 2.1.3 + compile + + + org.eclipse.parsson + parsson + 1.1.5 + compile + + + org.osgi + org.osgi.util.converter + 1.0.9 + compile + + org.slf4j slf4j-simple diff --git a/conga-sling-plugin/src/it/example/pom.xml b/conga-sling-plugin/src/it/example/pom.xml index 097b51d..41fc0b0 100644 --- a/conga-sling-plugin/src/it/example/pom.xml +++ b/conga-sling-plugin/src/it/example/pom.xml @@ -45,7 +45,7 @@ io.wcm.devops.conga conga-maven-plugin - 1.16.2 + 1.16.5-SNAPSHOT true diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ConfigurationHandler_ConfigAdmin184.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ConfigurationHandler_ConfigAdmin184.java deleted file mode 100644 index c2a1852..0000000 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ConfigurationHandler_ConfigAdmin184.java +++ /dev/null @@ -1,731 +0,0 @@ -/* - * #%L - * wcm.io - * %% - * Copyright (C) 2019 wcm.io - * %% - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * #L% - */ -package io.wcm.devops.conga.plugins.sling.util; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PushbackReader; -import java.io.Writer; -import java.lang.reflect.Array; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collection; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.apache.felix.cm.file.FilePersistenceManager; - -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -/* - * This file is COPIED from the sources of org.apache.felix.configadmin 1.8.4 - * to write the configuration using the old (single line) array style, which is required to support - * deploying configurations to old AEM version like AEM 6.1 and lower. - */ -/** - * The ConfigurationHandler class implements configuration reading - * form a java.io.InputStream and writing to a - * java.io.OutputStream on behalf of the - * {@link FilePersistenceManager} class. - * - *
- * cfg = prop "=" value .
- *  prop = symbolic-name . // 1.4.2 of OSGi Core Specification
- *  symbolic-name = token { "." token } .
- *  token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
- *  value = [ type ] ( "[" values "]" | "(" values ")" | simple ) .
- *  values = simple { "," simple } .
- *  simple = """ stringsimple """ .
- *  type = // 1-char type code .
- *  stringsimple = // quoted string representation of the value .
- * 
- */ -//CHECKSTYLE:OFF -@SuppressWarnings({ "unchecked", "unused", "javadoc", "PMD" }) -@SuppressFBWarnings({ "PERFORMANCE", "STYLE" }) -class ConfigurationHandler_ConfigAdmin184 { - - protected static final String ENCODING = "UTF-8"; - - protected static final int TOKEN_NAME = 'N'; - protected static final int TOKEN_EQ = '='; - protected static final int TOKEN_ARR_OPEN = '['; - protected static final int TOKEN_ARR_CLOS = ']'; - protected static final int TOKEN_VEC_OPEN = '('; - protected static final int TOKEN_VEC_CLOS = ')'; - protected static final int TOKEN_COMMA = ','; - protected static final int TOKEN_VAL_OPEN = '"'; // '{'; - protected static final int TOKEN_VAL_CLOS = '"'; // '}'; - - // simple types (string & primitive wrappers) - protected static final int TOKEN_SIMPLE_STRING = 'T'; - protected static final int TOKEN_SIMPLE_INTEGER = 'I'; - protected static final int TOKEN_SIMPLE_LONG = 'L'; - protected static final int TOKEN_SIMPLE_FLOAT = 'F'; - protected static final int TOKEN_SIMPLE_DOUBLE = 'D'; - protected static final int TOKEN_SIMPLE_BYTE = 'X'; - protected static final int TOKEN_SIMPLE_SHORT = 'S'; - protected static final int TOKEN_SIMPLE_CHARACTER = 'C'; - protected static final int TOKEN_SIMPLE_BOOLEAN = 'B'; - - // primitives - protected static final int TOKEN_PRIMITIVE_INT = 'i'; - protected static final int TOKEN_PRIMITIVE_LONG = 'l'; - protected static final int TOKEN_PRIMITIVE_FLOAT = 'f'; - protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd'; - protected static final int TOKEN_PRIMITIVE_BYTE = 'x'; - protected static final int TOKEN_PRIMITIVE_SHORT = 's'; - protected static final int TOKEN_PRIMITIVE_CHAR = 'c'; - protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b'; - - protected static final String CRLF = "\r\n"; - - protected static final Map code2Type; - protected static final Map type2Code; - - // set of valid characters for "symblic-name" - private static final BitSet NAME_CHARS; - private static final BitSet TOKEN_CHARS; - - static { - type2Code = new HashMap(); - - // simple (exclusive String whose type code is not written) - type2Code.put(Integer.class, Integer.valueOf(TOKEN_SIMPLE_INTEGER)); - type2Code.put(Long.class, Integer.valueOf(TOKEN_SIMPLE_LONG)); - type2Code.put(Float.class, Integer.valueOf(TOKEN_SIMPLE_FLOAT)); - type2Code.put(Double.class, Integer.valueOf(TOKEN_SIMPLE_DOUBLE)); - type2Code.put(Byte.class, Integer.valueOf(TOKEN_SIMPLE_BYTE)); - type2Code.put(Short.class, Integer.valueOf(TOKEN_SIMPLE_SHORT)); - type2Code.put(Character.class, Integer.valueOf(TOKEN_SIMPLE_CHARACTER)); - type2Code.put(Boolean.class, Integer.valueOf(TOKEN_SIMPLE_BOOLEAN)); - - // primitives - type2Code.put(Integer.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_INT)); - type2Code.put(Long.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_LONG)); - type2Code.put(Float.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_FLOAT)); - type2Code.put(Double.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_DOUBLE)); - type2Code.put(Byte.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_BYTE)); - type2Code.put(Short.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_SHORT)); - type2Code.put(Character.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_CHAR)); - type2Code.put(Boolean.TYPE, Integer.valueOf(TOKEN_PRIMITIVE_BOOLEAN)); - - // reverse map to map type codes to classes, string class mapping - // to be added manually, as the string type code is not written and - // hence not included in the type2Code map - code2Type = new HashMap(); - for (Iterator ti = type2Code.entrySet().iterator(); ti.hasNext();) { - Map.Entry entry = (Map.Entry)ti.next(); - code2Type.put(entry.getValue(), entry.getKey()); - } - code2Type.put(Integer.valueOf(TOKEN_SIMPLE_STRING), String.class); - - NAME_CHARS = new BitSet(); - for (int i = '0'; i <= '9'; i++) { - NAME_CHARS.set(i); - } - for (int i = 'a'; i <= 'z'; i++) { - NAME_CHARS.set(i); - } - for (int i = 'A'; i <= 'Z'; i++) { - NAME_CHARS.set(i); - } - NAME_CHARS.set('_'); - NAME_CHARS.set('-'); - NAME_CHARS.set('.'); - NAME_CHARS.set('\\'); - - TOKEN_CHARS = new BitSet(); - TOKEN_CHARS.set(TOKEN_EQ); - TOKEN_CHARS.set(TOKEN_ARR_OPEN); - TOKEN_CHARS.set(TOKEN_ARR_CLOS); - TOKEN_CHARS.set(TOKEN_VEC_OPEN); - TOKEN_CHARS.set(TOKEN_VEC_CLOS); - TOKEN_CHARS.set(TOKEN_COMMA); - TOKEN_CHARS.set(TOKEN_VAL_OPEN); - TOKEN_CHARS.set(TOKEN_VAL_CLOS); - TOKEN_CHARS.set(TOKEN_SIMPLE_STRING); - TOKEN_CHARS.set(TOKEN_SIMPLE_INTEGER); - TOKEN_CHARS.set(TOKEN_SIMPLE_LONG); - TOKEN_CHARS.set(TOKEN_SIMPLE_FLOAT); - TOKEN_CHARS.set(TOKEN_SIMPLE_DOUBLE); - TOKEN_CHARS.set(TOKEN_SIMPLE_BYTE); - TOKEN_CHARS.set(TOKEN_SIMPLE_SHORT); - TOKEN_CHARS.set(TOKEN_SIMPLE_CHARACTER); - TOKEN_CHARS.set(TOKEN_SIMPLE_BOOLEAN); - - // primitives - TOKEN_CHARS.set(TOKEN_PRIMITIVE_INT); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_LONG); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_FLOAT); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_DOUBLE); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_BYTE); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_SHORT); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_CHAR); - TOKEN_CHARS.set(TOKEN_PRIMITIVE_BOOLEAN); - } - - - /** - * Writes the configuration data from the Dictionary to the - * given OutputStream. - *

- * This method writes at the current location in the stream and does not - * close the outputstream. - * @param out - * The OutputStream to write the configurtion data - * to. - * @param properties - * The Dictionary to write. - * @throws IOException - * If an error occurrs writing to the output stream. - */ - public static void write(OutputStream out, Dictionary properties) throws IOException { - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(out, ENCODING)); - - for (Enumeration ce = properties.keys(); ce.hasMoreElements();) { - String key = (String)ce.nextElement(); - - // cfg = prop "=" value "." . - writeQuoted(bw, key); - bw.write(TOKEN_EQ); - writeValue(bw, properties.get(key)); - bw.write(CRLF); - } - - bw.flush(); - } - - - /** - * Reads configuration data from the given InputStream and - * returns a new Dictionary object containing the data. - *

- * This method reads from the current location in the stream upto the end of - * the stream but does not close the stream at the end. - * @param ins - * The InputStream from which to read the - * configuration data. - * @return A Dictionary object containing the configuration - * data. This object may be empty if the stream contains no - * configuration data. - * @throws IOException - * If an error occurrs reading from the stream. This exception - * is also thrown if a syntax error is encountered. - */ - public static Dictionary read(InputStream ins) throws IOException { - return new ConfigurationHandler_ConfigAdmin184().readInternal(ins); - } - - - // private constructor, this class is not to be instantiated from the - // outside - private ConfigurationHandler_ConfigAdmin184() {} - - // ---------- Configuration Input Implementation --------------------------- - - private int token; - private String tokenValue; - private int line; - private int pos; - - - private Dictionary readInternal(InputStream ins) throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(ins, ENCODING)); - PushbackReader pr = new PushbackReader(br, 1); - - token = 0; - tokenValue = null; - line = 0; - pos = 0; - - Hashtable configuration = new Hashtable(); - token = 0; - while (nextToken(pr) == TOKEN_NAME) { - String key = tokenValue; - - // expect equal sign - if (nextToken(pr) != TOKEN_EQ) { - throw readFailure(token, TOKEN_EQ); - } - - // expect the token value - Object value = readValue(pr); - if (value != null) { - configuration.put(key, value); - } - } - - return configuration; - } - - - /** - * value = type ( "[" values "]" | "(" values ")" | simple ) . values = - * value { "," value } . simple = "{" stringsimple "}" . type = // 1-char - * type code . stringsimple = // quoted string representation of the value . - * @param pr - * @return - * @throws IOException - */ - private Object readValue(PushbackReader pr) throws IOException { - // read (optional) type code - int type = read(pr); - - // read value kind code if type code is not a value kinde code - int code; - if (code2Type.containsKey(Integer.valueOf(type))) { - code = read(pr); - } - else { - code = type; - type = TOKEN_SIMPLE_STRING; - } - - switch (code) { - case TOKEN_ARR_OPEN: - return readArray(type, pr); - - case TOKEN_VEC_OPEN: - return readCollection(type, pr); - - case TOKEN_VAL_OPEN: - Object value = readSimple(type, pr); - ensureNext(pr, TOKEN_VAL_CLOS); - return value; - - default: - return null; - } - } - - - private Object readArray(int typeCode, PushbackReader pr) throws IOException { - List list = new ArrayList(); - for (;;) { - int c = read(pr); - if (c == TOKEN_VAL_OPEN) { - Object value = readSimple(typeCode, pr); - if (value == null) { - // abort due to error - return null; - } - - ensureNext(pr, TOKEN_VAL_CLOS); - - list.add(value); - - c = read(pr); - } - - if (c == TOKEN_ARR_CLOS) { - Class type = (Class)code2Type.get(Integer.valueOf(typeCode)); - Object array = Array.newInstance(type, list.size()); - for (int i = 0; i < list.size(); i++) { - Array.set(array, i, list.get(i)); - } - return array; - } - else if (c < 0) { - return null; - } - else if (c != TOKEN_COMMA) { - return null; - } - } - } - - - private Collection readCollection(int typeCode, PushbackReader pr) throws IOException { - Collection collection = new ArrayList(); - for (;;) { - int c = read(pr); - if (c == TOKEN_VAL_OPEN) { - Object value = readSimple(typeCode, pr); - if (value == null) { - // abort due to error - return null; - } - - ensureNext(pr, TOKEN_VAL_CLOS); - - collection.add(value); - - c = read(pr); - } - - if (c == TOKEN_VEC_CLOS) { - return collection; - } - else if (c < 0) { - return null; - } - else if (c != TOKEN_COMMA) { - return null; - } - } - } - - - private Object readSimple(int code, PushbackReader pr) throws IOException { - switch (code) { - case -1: - return null; - - case TOKEN_SIMPLE_STRING: - return readQuoted(pr); - - // Simple/Primitive, only use wrapper classes - case TOKEN_SIMPLE_INTEGER: - case TOKEN_PRIMITIVE_INT: - return Integer.valueOf(readQuoted(pr)); - - case TOKEN_SIMPLE_LONG: - case TOKEN_PRIMITIVE_LONG: - return Long.valueOf(readQuoted(pr)); - - case TOKEN_SIMPLE_FLOAT: - case TOKEN_PRIMITIVE_FLOAT: - int fBits = Integer.parseInt(readQuoted(pr)); - return Float.valueOf(Float.intBitsToFloat(fBits)); - - case TOKEN_SIMPLE_DOUBLE: - case TOKEN_PRIMITIVE_DOUBLE: - long dBits = Long.parseLong(readQuoted(pr)); - return Double.valueOf(Double.longBitsToDouble(dBits)); - - case TOKEN_SIMPLE_BYTE: - case TOKEN_PRIMITIVE_BYTE: - return Byte.valueOf(readQuoted(pr)); - - case TOKEN_SIMPLE_SHORT: - case TOKEN_PRIMITIVE_SHORT: - return Short.valueOf(readQuoted(pr)); - - case TOKEN_SIMPLE_CHARACTER: - case TOKEN_PRIMITIVE_CHAR: - String cString = readQuoted(pr); - if (cString != null && cString.length() > 0) { - return Character.valueOf(cString.charAt(0)); - } - return null; - - case TOKEN_SIMPLE_BOOLEAN: - case TOKEN_PRIMITIVE_BOOLEAN: - return Boolean.valueOf(readQuoted(pr)); - - // unknown type code - default: - return null; - } - } - - - private void ensureNext(PushbackReader pr, int expected) throws IOException { - int next = read(pr); - if (next != expected) { - readFailure(next, expected); - } - } - - - private boolean checkNext(PushbackReader pr, int expected) throws IOException { - int next = read(pr); - if (next < 0) { - return false; - } - - if (next == expected) { - return true; - } - - return false; - } - - - private String readQuoted(PushbackReader pr) throws IOException { - StringBuffer buf = new StringBuffer(); - for (;;) { - int c = read(pr); - switch (c) { - // escaped character - case '\\': - c = read(pr); - switch (c) { - // well known escapes - case 'b': - buf.append('\b'); - break; - case 't': - buf.append('\t'); - break; - case 'n': - buf.append('\n'); - break; - case 'f': - buf.append('\f'); - break; - case 'r': - buf.append('\r'); - break; - case 'u':// need 4 characters ! - char[] cbuf = new char[4]; - if (read(pr, cbuf) == 4) { - c = Integer.parseInt(new String(cbuf), 16); - buf.append((char)c); - } - break; - - // just an escaped character, unescape - default: - buf.append((char)c); - } - break; - - // eof - case -1: // fall through - - // separator token - case TOKEN_EQ: - case TOKEN_VAL_CLOS: - pr.unread(c); - return buf.toString(); - - // no escaping - default: - buf.append((char)c); - } - } - } - - - private int nextToken(PushbackReader pr) throws IOException { - int c = ignorableWhiteSpace(pr); - - // immediately return EOF - if (c < 0) { - return (token = c); - } - - // check whether there is a name - if (NAME_CHARS.get(c) || !TOKEN_CHARS.get(c)) { - // read the property name - pr.unread(c); - tokenValue = readQuoted(pr); - return (token = TOKEN_NAME); - } - - // check another token - if (TOKEN_CHARS.get(c)) { - return (token = c); - } - - // unexpected character -> so what ?? - return (token = -1); - } - - - private int ignorableWhiteSpace(PushbackReader pr) throws IOException { - int c = read(pr); - while (c >= 0 && Character.isWhitespace((char)c)) { - c = read(pr); - } - return c; - } - - - private int read(PushbackReader pr) throws IOException { - int c = pr.read(); - if (c == '\r') { - int c1 = pr.read(); - if (c1 != '\n') { - pr.unread(c1); - } - c = '\n'; - } - - if (c == '\n') { - line++; - pos = 0; - } - else { - pos++; - } - - return c; - } - - - private int read(PushbackReader pr, char[] buf) throws IOException { - for (int i = 0; i < buf.length; i++) { - int c = read(pr); - if (c >= 0) { - buf[i] = (char)c; - } - else { - return i; - } - } - - return buf.length; - } - - - private IOException readFailure(int current, int expected) { - return new IOException("Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos=" - + pos + ")"); - } - - - // ---------- Configuration Output Implementation -------------------------- - - private static void writeValue(Writer out, Object value) throws IOException { - Class clazz = value.getClass(); - if (clazz.isArray()) { - writeArray(out, value); - } - else if (value instanceof Collection) { - writeCollection(out, (Collection)value); - } - else { - writeType(out, clazz); - writeSimple(out, value); - } - } - - - private static void writeArray(Writer out, Object arrayValue) throws IOException { - int size = Array.getLength(arrayValue); - writeType(out, arrayValue.getClass().getComponentType()); - out.write(TOKEN_ARR_OPEN); - for (int i = 0; i < size; i++) { - if (i > 0) { - out.write(TOKEN_COMMA); - } - writeSimple(out, Array.get(arrayValue, i)); - } - out.write(TOKEN_ARR_CLOS); - } - - - private static void writeCollection(Writer out, Collection collection) throws IOException { - if (collection.isEmpty()) { - out.write(TOKEN_VEC_OPEN); - out.write(TOKEN_VEC_CLOS); - } - else { - Iterator ci = collection.iterator(); - Object firstElement = ci.next(); - - writeType(out, firstElement.getClass()); - out.write(TOKEN_VEC_OPEN); - writeSimple(out, firstElement); - - while (ci.hasNext()) { - out.write(TOKEN_COMMA); - writeSimple(out, ci.next()); - } - out.write(TOKEN_VEC_CLOS); - } - } - - - private static void writeType(Writer out, Class valueType) throws IOException { - Integer code = (Integer)type2Code.get(valueType); - if (code != null) { - out.write((char)code.intValue()); - } - } - - - private static void writeSimple(Writer out, Object value) throws IOException { - if (value instanceof Double) { - double dVal = ((Double)value).doubleValue(); - value = Long.valueOf(Double.doubleToRawLongBits(dVal)); - } - else if (value instanceof Float) { - float fVal = ((Float)value).floatValue(); - value = Integer.valueOf(Float.floatToRawIntBits(fVal)); - } - - out.write(TOKEN_VAL_OPEN); - writeQuoted(out, String.valueOf(value)); - out.write(TOKEN_VAL_CLOS); - } - - - private static void writeQuoted(Writer out, String simple) throws IOException { - if (simple == null || simple.length() == 0) { - return; - } - - char c = 0; - int len = simple.length(); - for (int i = 0; i < len; i++) { - c = simple.charAt(i); - switch (c) { - case '\\': - case TOKEN_VAL_CLOS: - case ' ': - case TOKEN_EQ: - out.write('\\'); - out.write(c); - break; - - // well known escapes - case '\b': - out.write("\\b"); - break; - case '\t': - out.write("\\t"); - break; - case '\n': - out.write("\\n"); - break; - case '\f': - out.write("\\f"); - break; - case '\r': - out.write("\\r"); - break; - - // other escaping - default: - if (c < ' ') { - String t = "000" + Integer.toHexString(c); - out.write("\\u" + t.substring(t.length() - 4)); - } - else { - out.write(c); - } - } - } - } -} diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/OsgiConfigUtil.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/OsgiConfigUtil.java index e7d42e8..1c64d7b 100644 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/OsgiConfigUtil.java +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/OsgiConfigUtil.java @@ -21,11 +21,15 @@ import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.nio.charset.StandardCharsets; import java.util.Dictionary; +import org.apache.felix.cm.json.io.Configurations; + /** - * Helper class for writing OSGi configurations in Felix ConfigAdmin format. - * This writes the "old" ConfigAdmin format of Felix ConfigAdmin 1.8.4 to be compatible with AEM 6.1 and below. + * Helper class for writing OSGi configurations in JSON format (.cfg.json files). */ public final class OsgiConfigUtil { @@ -42,8 +46,10 @@ private OsgiConfigUtil() { * @param properties The Dictionary to write. * @throws IOException If an error occurs writing to the output stream. */ - public static void write(OutputStream out, Dictionary properties) throws IOException { - ConfigurationHandler_ConfigAdmin184.write(out, properties); + public static void write(OutputStream out, Dictionary properties) throws IOException { + try (Writer writer = new OutputStreamWriter(out, StandardCharsets.UTF_8)) { + Configurations.buildWriter().build(writer).writeConfiguration(properties); + } } } diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ProvisioningUtil.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ProvisioningUtil.java index b9a9f09..80aeb32 100644 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ProvisioningUtil.java +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/ProvisioningUtil.java @@ -179,7 +179,7 @@ private static String getPathForConfiguration(Configuration configuration, RunMo if (configuration.getFactoryPid() != null) { path.append(configuration.getFactoryPid()).append("-"); } - path.append(configuration.getPid()).append(".config"); + path.append(configuration.getPid()).append(".cfg.json"); return path.toString(); } diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessorTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessorTest.java index c7ab868..0b15e1c 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessorTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessorTest.java @@ -25,16 +25,14 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.BufferedInputStream; import java.io.File; -import java.io.FileInputStream; +import java.io.FileReader; import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Dictionary; import org.apache.commons.io.FileUtils; -import org.apache.felix.cm.file.ConfigurationHandler; +import org.apache.felix.cm.json.io.Configurations; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInfo; @@ -72,7 +70,7 @@ void testProvisioningExample() throws Exception { postProcess(provisioningFile); // validate generated configs - Dictionary config = readConfig("my.pid.config"); + Dictionary config = readConfig("my.pid.cfg.json"); assertEquals("value1", config.get("stringProperty")); assertArrayEquals(new String[] { "v1", "v2", "v3" @@ -80,20 +78,20 @@ void testProvisioningExample() throws Exception { assertEquals(true, config.get("booleanProperty")); assertEquals(999999999999L, config.get("longProperty")); - assertExists("my.factory-my.pid.config"); - assertExists("mode1/my.factory-my.pid2.config"); - assertExists("mode2/my.pid2.config"); - assertExists("publish.prod/my.pid2.config"); + assertExists("my.factory-my.pid.cfg.json"); + assertExists("mode1/my.factory-my.pid2.cfg.json"); + assertExists("mode2/my.pid2.cfg.json"); + assertExists("publish.prod/my.pid2.cfg.json"); // validate repoinit statements - config = readConfig("org.apache.sling.jcr.repoinit.RepositoryInitializer-test.config"); + config = readConfig("org.apache.sling.jcr.repoinit.RepositoryInitializer-test.cfg.json"); assertArrayEquals(new String[] {"create path /repoinit/test1\n" + "create path /repoinit/test2\n" }, (String[])config.get("scripts")); - config = readConfig("mode1/org.apache.sling.jcr.repoinit.RepositoryInitializer-test-mode1.config"); + config = readConfig("mode1/org.apache.sling.jcr.repoinit.RepositoryInitializer-test-mode1.cfg.json"); assertArrayEquals(new String[] { "create service user mode1\n" }, (String[])config.get("scripts")); - config = readConfig("mode1.mode2/org.apache.sling.jcr.repoinit.RepositoryInitializer-test-mode1-mode2.config"); + config = readConfig("mode1.mode2/org.apache.sling.jcr.repoinit.RepositoryInitializer-test-mode1-mode2.cfg.json"); assertArrayEquals(new String[] { "create service user mode1_mode2" }, (String[])config.get("scripts")); } @@ -111,7 +109,7 @@ void testSimpleConfig() throws Exception { postProcess(provisioningFile); // validate generated configs - Dictionary config = readConfig("com.example.ServiceConfiguration.config"); + Dictionary config = readConfig("com.example.ServiceConfiguration.cfg.json"); assertEquals("bar", config.get("bar")); assertEquals("foo", config.get("foo")); } @@ -130,7 +128,7 @@ void testSimpleConfigWithNewline() throws Exception { postProcess(provisioningFile); // validate generated configs - Dictionary config = readConfig("com.example.ServiceConfiguration.config"); + Dictionary config = readConfig("com.example.ServiceConfiguration.cfg.json"); assertNull(config.get("bar")); assertEquals("foo", config.get("foo")); } @@ -144,7 +142,7 @@ void testEscapedVariable() throws Exception { postProcess(provisioningFile); // validate generated configs - Dictionary config = readConfig("my.pid.config"); + Dictionary config = readConfig("my.pid.cfg.json"); assertEquals("${var1} and ${var2}", config.get("stringProperty")); } @@ -169,8 +167,8 @@ private void postProcess(File provisioningFile) { private Dictionary readConfig(String fileName) throws IOException { assertExists(fileName); File file = new File(targetDir, fileName); - try (InputStream is = new BufferedInputStream(new FileInputStream(file))) { - return ConfigurationHandler.read(is); + try (FileReader reader = new FileReader(file, StandardCharsets.UTF_8)) { + return Configurations.buildReader().build(reader).readConfiguration(); } } diff --git a/conga-sling-plugin/src/test/resources/configSample.cfg.json b/conga-sling-plugin/src/test/resources/configSample.cfg.json new file mode 100644 index 0000000..9b83410 --- /dev/null +++ b/conga-sling-plugin/src/test/resources/configSample.cfg.json @@ -0,0 +1,5 @@ +{ + "prop1": "value1", + "prop2": 5, + "prop3": true +} diff --git a/parent/pom.xml b/parent/pom.xml index 0009890..c1125ba 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -31,7 +31,7 @@ io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.parent - 1.3.5-SNAPSHOT + 1.4.0-SNAPSHOT pom CONGA Sling Plugin diff --git a/pom.xml b/pom.xml index 36d0616..83c7a6f 100644 --- a/pom.xml +++ b/pom.xml @@ -23,13 +23,13 @@ io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.parent - 1.3.5-SNAPSHOT + 1.4.0-SNAPSHOT parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.root - 1.3.5-SNAPSHOT + 1.4.0-SNAPSHOT pom CONGA Sling Plugin