From a81b1e05eaef3a1456752b4dbc90cc42de5a7b19 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Mon, 20 Nov 2023 13:36:34 +0100 Subject: [PATCH 01/16] [gitflow-maven-plugin] Update for next development version 1.3.5-SNAPSHOT --- conga-sling-plugin/pom.xml | 4 ++-- parent/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index fc27846..cb1b723 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.4 + 1.3.5-SNAPSHOT ../parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling - 1.3.4 + 1.3.5-SNAPSHOT jar CONGA Sling Plugin diff --git a/parent/pom.xml b/parent/pom.xml index e8038dd..0009890 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.4 + 1.3.5-SNAPSHOT pom CONGA Sling Plugin diff --git a/pom.xml b/pom.xml index c34f117..36d0616 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.4 + 1.3.5-SNAPSHOT parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.root - 1.3.4 + 1.3.5-SNAPSHOT pom CONGA Sling Plugin From 17ddd48c9ae12a6efbbb89060367540774c0c198 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 9 Jan 2024 15:27:45 +0100 Subject: [PATCH 02/16] 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 From 31fbc3b772447531f09a3ccb452606b5b74a506d Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 9 Jan 2024 15:52:21 +0100 Subject: [PATCH 03/16] OsgiCfgJsonFileHeader was removed --- changes.xml | 3 --- 1 file changed, 3 deletions(-) diff --git a/changes.xml b/changes.xml index 4c4239a..6979831 100644 --- a/changes.xml +++ b/changes.xml @@ -24,9 +24,6 @@ - - Add OsgiCfgJsonFileHeader to provide file header support for .cfg.json files. - ProvisioningOsgiConfigPostProcessor: Write OSGi configurations as .cfg.json files instead of .config files. From 234981c687b3ff627c518ef696866cc71191b5a1 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 9 Jan 2024 16:04:09 +0100 Subject: [PATCH 04/16] remove obsolete test resource --- conga-sling-plugin/src/test/resources/configSample.cfg.json | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 conga-sling-plugin/src/test/resources/configSample.cfg.json diff --git a/conga-sling-plugin/src/test/resources/configSample.cfg.json b/conga-sling-plugin/src/test/resources/configSample.cfg.json deleted file mode 100644 index 9b83410..0000000 --- a/conga-sling-plugin/src/test/resources/configSample.cfg.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "prop1": "value1", - "prop2": 5, - "prop3": true -} From ae0ddf89345ba25e9292defd430f94e81bfb864f Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Tue, 9 Jan 2024 16:06:18 +0100 Subject: [PATCH 05/16] move test resources to sub folder --- .../sling/fileheader/ProvisioningFileHeaderTest.java | 2 +- .../ProvisioningOsgiConfigPostProcessorTest.java | 4 ++-- .../sling/validator/ProvisioningValidatorTest.java | 8 ++++---- .../resources/{ => provisioning}/invalidProvisioning.txt | 0 .../test/resources/{ => provisioning}/noProvisioning.txt | 0 .../resources/{ => provisioning}/validProvisioning.txt | 0 .../validProvisioningEscapedVariable.txt | 0 7 files changed, 7 insertions(+), 7 deletions(-) rename conga-sling-plugin/src/test/resources/{ => provisioning}/invalidProvisioning.txt (100%) rename conga-sling-plugin/src/test/resources/{ => provisioning}/noProvisioning.txt (100%) rename conga-sling-plugin/src/test/resources/{ => provisioning}/validProvisioning.txt (100%) rename conga-sling-plugin/src/test/resources/{ => provisioning}/validProvisioningEscapedVariable.txt (100%) diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java index 392f55e..a2c0215 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java @@ -50,7 +50,7 @@ void setUp() { @Test void testApply() throws Exception { File file = new File("target/generation-test/fileHeader.txt"); - FileUtils.copyFile(new File(getClass().getResource("/validProvisioning.txt").toURI()), file); + FileUtils.copyFile(new File(getClass().getResource("/provisioning/validProvisioning.txt").toURI()), file); List lines = ImmutableList.of("Der Jodelkaiser", "aus dem Oetztal", "ist wieder daheim."); FileHeaderContext context = new FileHeaderContext().commentLines(lines); 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 0b15e1c..1c0b609 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 @@ -66,7 +66,7 @@ void testProvisioningExample() throws Exception { // post process example valid provisioning file File provisioningFile = new File(targetDir, "provisioningExample.txt"); - FileUtils.copyFile(new File(getClass().getResource("/validProvisioning.txt").toURI()), provisioningFile); + FileUtils.copyFile(new File(getClass().getResource("/provisioning/validProvisioning.txt").toURI()), provisioningFile); postProcess(provisioningFile); // validate generated configs @@ -138,7 +138,7 @@ void testEscapedVariable() throws Exception { // post process example valid provisioning file File provisioningFile = new File(targetDir, "provisioningExample.txt"); - FileUtils.copyFile(new File(getClass().getResource("/validProvisioningEscapedVariable.txt").toURI()), provisioningFile); + FileUtils.copyFile(new File(getClass().getResource("/provisioning/validProvisioningEscapedVariable.txt").toURI()), provisioningFile); postProcess(provisioningFile); // validate generated configs diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/validator/ProvisioningValidatorTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/validator/ProvisioningValidatorTest.java index a0a3ed4..23af1d4 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/validator/ProvisioningValidatorTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/validator/ProvisioningValidatorTest.java @@ -45,7 +45,7 @@ void setUp() { @Test void testValid() throws Exception { - File file = new File(getClass().getResource("/validProvisioning.txt").toURI()); + File file = new File(getClass().getResource("/provisioning/validProvisioning.txt").toURI()); FileContext fileContext = new FileContext().file(file).charset(StandardCharsets.UTF_8); assertTrue(underTest.accepts(fileContext, null)); underTest.apply(fileContext, null); @@ -53,7 +53,7 @@ void testValid() throws Exception { @Test void testInvalid() throws Exception { - File file = new File(getClass().getResource("/invalidProvisioning.txt").toURI()); + File file = new File(getClass().getResource("/provisioning/invalidProvisioning.txt").toURI()); FileContext fileContext = new FileContext().file(file).charset(StandardCharsets.UTF_8); assertTrue(underTest.accepts(fileContext, null)); assertThrows(ValidationException.class, () -> { @@ -63,14 +63,14 @@ void testInvalid() throws Exception { @Test void testInvalidFileExtension() throws Exception { - File file = new File(getClass().getResource("/noProvisioning.txt").toURI()); + File file = new File(getClass().getResource("/provisioning/noProvisioning.txt").toURI()); FileContext fileContext = new FileContext().file(file).charset(StandardCharsets.UTF_8); assertFalse(underTest.accepts(fileContext, null)); } @Test void testEscapedVariable() throws Exception { - File file = new File(getClass().getResource("/validProvisioningEscapedVariable.txt").toURI()); + File file = new File(getClass().getResource("/provisioning/validProvisioningEscapedVariable.txt").toURI()); FileContext fileContext = new FileContext().file(file).charset(StandardCharsets.UTF_8); assertTrue(underTest.accepts(fileContext, null)); underTest.apply(fileContext, null); diff --git a/conga-sling-plugin/src/test/resources/invalidProvisioning.txt b/conga-sling-plugin/src/test/resources/provisioning/invalidProvisioning.txt similarity index 100% rename from conga-sling-plugin/src/test/resources/invalidProvisioning.txt rename to conga-sling-plugin/src/test/resources/provisioning/invalidProvisioning.txt diff --git a/conga-sling-plugin/src/test/resources/noProvisioning.txt b/conga-sling-plugin/src/test/resources/provisioning/noProvisioning.txt similarity index 100% rename from conga-sling-plugin/src/test/resources/noProvisioning.txt rename to conga-sling-plugin/src/test/resources/provisioning/noProvisioning.txt diff --git a/conga-sling-plugin/src/test/resources/validProvisioning.txt b/conga-sling-plugin/src/test/resources/provisioning/validProvisioning.txt similarity index 100% rename from conga-sling-plugin/src/test/resources/validProvisioning.txt rename to conga-sling-plugin/src/test/resources/provisioning/validProvisioning.txt diff --git a/conga-sling-plugin/src/test/resources/validProvisioningEscapedVariable.txt b/conga-sling-plugin/src/test/resources/provisioning/validProvisioningEscapedVariable.txt similarity index 100% rename from conga-sling-plugin/src/test/resources/validProvisioningEscapedVariable.txt rename to conga-sling-plugin/src/test/resources/provisioning/validProvisioningEscapedVariable.txt From b9dc6e2397bb8f1995d609031537c32fcdb52bc2 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 10 Jan 2024 12:28:52 +0100 Subject: [PATCH 06/16] Add JsonOsgiConfigPostProcessor to support reading a combined set of OSGi configuration for run modes from .osgiconfig.json files (#7) --- changes.xml | 3 + conga-sling-plugin/pom.xml | 7 + .../src/it/example/src/main/roles/sling.yaml | 5 + .../sling/config-sample.osgiconfig.json.hbs | 17 ++ .../sling/sling-provisioning.provisioning.hbs | 4 + .../JsonOsgiConfigPostProcessor.java | 85 +++++++ .../ProvisioningOsgiConfigPostProcessor.java | 39 +--- .../sling/util/JsonOsgiConfigUtil.java | 212 ++++++++++++++++++ .../plugins/sling/util/ProvisioningUtil.java | 38 +++- ...ps.conga.generator.spi.PostProcessorPlugin | 1 + .../JsonOsgiConfigPostProcessorTest.java | 128 +++++++++++ ...ovisioningOsgiConfigPostProcessorTest.java | 4 +- .../sling/util/JsonOsgiConfigUtilTest.java | 49 ++++ .../src/test/resources/arrayTypes.json | 10 + .../osgi-config-json/sample.osgiconfig.json | 47 ++++ src/site/markdown/index.md | 8 +- .../markdown/osgi-config-combined-json.md | 54 +++++ 17 files changed, 671 insertions(+), 40 deletions(-) create mode 100644 conga-sling-plugin/src/it/example/src/main/templates/sling/config-sample.osgiconfig.json.hbs create mode 100644 conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessor.java create mode 100644 conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java create mode 100644 conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java create mode 100644 conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java create mode 100644 conga-sling-plugin/src/test/resources/arrayTypes.json create mode 100644 conga-sling-plugin/src/test/resources/osgi-config-json/sample.osgiconfig.json create mode 100644 src/site/markdown/osgi-config-combined-json.md diff --git a/changes.xml b/changes.xml index 6979831..5eff9c0 100644 --- a/changes.xml +++ b/changes.xml @@ -24,6 +24,9 @@ + + Add JsonOsgiConfigPostProcessor to support reading a combined set of OSGi configuration for run modes from .osgiconfig.json files. + ProvisioningOsgiConfigPostProcessor: Write OSGi configurations as .cfg.json files instead of .config files. diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index 3a9cab8..9b7917d 100644 --- a/conga-sling-plugin/pom.xml +++ b/conga-sling-plugin/pom.xml @@ -79,6 +79,13 @@ compile + + com.fasterxml.jackson.core + jackson-databind + 2.16.1 + compile + + org.apache.felix org.apache.felix.cm.json diff --git a/conga-sling-plugin/src/it/example/src/main/roles/sling.yaml b/conga-sling-plugin/src/it/example/src/main/roles/sling.yaml index b8cdd79..f0fbc94 100644 --- a/conga-sling-plugin/src/it/example/src/main/roles/sling.yaml +++ b/conga-sling-plugin/src/it/example/src/main/roles/sling.yaml @@ -13,6 +13,11 @@ files: postProcessors: - sling-provisioning-osgiconfig +- file: config-sample.osgiconfig.json + dir: osgi-config-from-json + template: config-sample.osgiconfig.json.hbs + postProcessors: + - sling-json-osgiconfig # Defines configuration parameters and default values config: diff --git a/conga-sling-plugin/src/it/example/src/main/templates/sling/config-sample.osgiconfig.json.hbs b/conga-sling-plugin/src/it/example/src/main/templates/sling/config-sample.osgiconfig.json.hbs new file mode 100644 index 0000000..ae9bb32 --- /dev/null +++ b/conga-sling-plugin/src/it/example/src/main/templates/sling/config-sample.osgiconfig.json.hbs @@ -0,0 +1,17 @@ +{ + "configurations": { + "my.pid": { + "heapspaceMax": "{{jvm.heapspace.max}}", + "booleanProp": true, + "numberProp": 123, + "arrayProp": ["v1","v2","v3"], + "numberArrayProp": [1,2] + } + }, + "configurations:mode1": { + "my.pid2": { + "stringProperty": "{{var1}}", + "stringProperty2": "{{var2}}" + } + } +} diff --git a/conga-sling-plugin/src/it/example/src/main/templates/sling/sling-provisioning.provisioning.hbs b/conga-sling-plugin/src/it/example/src/main/templates/sling/sling-provisioning.provisioning.hbs index 79f53dc..056c0f6 100644 --- a/conga-sling-plugin/src/it/example/src/main/templates/sling/sling-provisioning.provisioning.hbs +++ b/conga-sling-plugin/src/it/example/src/main/templates/sling/sling-provisioning.provisioning.hbs @@ -4,6 +4,10 @@ my.pid heapspaceMax="{{jvm.heapspace.max}}" + booleanProp=B"true" + numberProp=I"123" + arrayProp=["v1","v2","v3"] + numberArrayProp=I["1","2"] [configurations runModes=mode1] diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessor.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessor.java new file mode 100644 index 0000000..46c5aaa --- /dev/null +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessor.java @@ -0,0 +1,85 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 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.postprocessor; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.provisioning.model.Model; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import io.wcm.devops.conga.generator.GeneratorException; +import io.wcm.devops.conga.generator.spi.PostProcessorPlugin; +import io.wcm.devops.conga.generator.spi.context.FileContext; +import io.wcm.devops.conga.generator.spi.context.PostProcessorContext; +import io.wcm.devops.conga.plugins.sling.util.JsonOsgiConfigUtil; +import io.wcm.devops.conga.plugins.sling.util.ProvisioningUtil; + +/** + * Transforms a combined JSON file containing OSGi configurations into individual OSGi configuration files. + */ +public class JsonOsgiConfigPostProcessor implements PostProcessorPlugin { + + /** + * Plugin name + */ + public static final String NAME = "sling-json-osgiconfig"; + + /** + * File extension + */ + public static final String FILE_EXTENSION = ".osgiconfig.json"; + + @Override + public String getName() { + return NAME; + } + + @Override + public boolean accepts(FileContext file, PostProcessorContext context) { + return StringUtils.endsWith(file.getFile().getName(), FILE_EXTENSION); + } + + @Override + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") + public List apply(FileContext fileContext, PostProcessorContext context) { + File file = fileContext.getFile(); + try { + // read JSON file with combined configurations + Model model = JsonOsgiConfigUtil.readToProvisioningModel(file); + + // generate OSGi configurations + List files = ProvisioningUtil.generateOsgiConfigurations(model, file.getParentFile(), context); + + // delete provisioning file after transformation + Files.delete(file.toPath()); + + // return list of generated osgi configuration files + return files; + } + catch (IOException ex) { + throw new GeneratorException("Unable to parse JSON file with OSGi configurations.", ex); + } + } + +} diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessor.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessor.java index 9a759e7..9f855bf 100644 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessor.java +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/postprocessor/ProvisioningOsgiConfigPostProcessor.java @@ -20,10 +20,8 @@ package io.wcm.devops.conga.plugins.sling.postprocessor; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Dictionary; +import java.nio.file.Files; import java.util.List; import org.apache.sling.provisioning.model.Model; @@ -33,12 +31,11 @@ import io.wcm.devops.conga.generator.spi.PostProcessorPlugin; import io.wcm.devops.conga.generator.spi.context.FileContext; import io.wcm.devops.conga.generator.spi.context.PostProcessorContext; -import io.wcm.devops.conga.plugins.sling.util.ConfigConsumer; -import io.wcm.devops.conga.plugins.sling.util.OsgiConfigUtil; import io.wcm.devops.conga.plugins.sling.util.ProvisioningUtil; /** - * Transforms a Sling Provisioning file into OSGi configurations (ignoring all other provisioning contents). + * Transforms a Sling Provisioning file into OSGi configuration files (.cfg.json). + * Repoinit statements are supported as well, all other provisioning contents are ignored */ public class ProvisioningOsgiConfigPostProcessor implements PostProcessorPlugin { @@ -64,41 +61,17 @@ public List apply(FileContext fileContext, PostProcessorContext con try { // generate OSGi configurations Model model = ProvisioningUtil.getModel(fileContext); - List files = generateOsgiConfigurations(model, file.getParentFile(), context); + List files = ProvisioningUtil.generateOsgiConfigurations(model, file.getParentFile(), context); // delete provisioning file after transformation - file.delete(); + Files.delete(file.toPath()); // return list of generated osgi configuration files return files; } catch (IOException ex) { - throw new GeneratorException("Unable to post-process sling provisioning OSGi configurations.", ex); + throw new GeneratorException("Unable to post-process Sling Provisioning OSGi configurations.", ex); } } - /** - * Generate OSGi configuration for all feature and run modes. - * @param model Provisioning Model - * @param dir Target directory - * @param context Post processor context - */ - private List generateOsgiConfigurations(Model model, File dir, PostProcessorContext context) throws IOException { - return ProvisioningUtil.visitOsgiConfigurations(model, new ConfigConsumer() { - @Override - @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") - public FileContext accept(String path, Dictionary properties) throws IOException { - context.getLogger().info(" Generate {}", path); - - File confFile = new File(dir, path); - confFile.getParentFile().mkdirs(); - try (FileOutputStream os = new FileOutputStream(confFile)) { - OsgiConfigUtil.write(os, properties); - } - - return new FileContext().file(confFile).charset(StandardCharsets.UTF_8); - } - }); - } - } 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 new file mode 100644 index 0000000..bb01a63 --- /dev/null +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtil.java @@ -0,0 +1,212 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 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.File; +import java.io.IOException; +import java.lang.reflect.Array; +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; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.sling.provisioning.model.Configuration; +import org.apache.sling.provisioning.model.Feature; +import org.apache.sling.provisioning.model.Model; +import org.apache.sling.provisioning.model.RunMode; +import org.apache.sling.provisioning.model.Section; +import org.jetbrains.annotations.Nullable; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.type.MapType; + +import io.wcm.devops.conga.plugins.sling.postprocessor.JsonOsgiConfigPostProcessor; + +/** + * Transforms a combined JSON file to provisioning model with OSGi configurations and repoinit statements. + */ +public final class JsonOsgiConfigUtil { + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() + .enable(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS); + 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(:(.*))?$"); + private static final int RUNMODES_INDEX = 2; + + private JsonOsgiConfigUtil() { + // static methods only + } + + /** + * Read JSON file content to a map. + * @param file JSON file + * @return Map containing JSON content + * @throws IOException I/O exception + */ + static Map readToMap(File file) throws IOException { + String jsonString = FileUtils.readFileToString(file, StandardCharsets.UTF_8); + 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 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) { + Collection collection = ((Collection)value); + value = collection.toArray((Object[])Array.newInstance(detectArrayType(collection), collection.size())); + } + else if (value instanceof Map) { + value = convertListsToArrays((Map)value); + } + result.put(key, value); + } + return result; + } + + /** + * Detect type from list of items. If all items have the same type this is returned, otherwise Object.class. + */ + private static Class detectArrayType(Collection list) { + Class type = null; + for (Object item : list) { + if (item != null) { + if (type == null) { + type = item.getClass(); + } + else if (type != item.getClass()) { + type = Object.class; + } + } + } + if (type == null) { + type = Object.class; + } + return type; + } + + /** + * Read JSON file content to a map. + * @param file JSON file + * @return Map containing JSON content + * @throws IOException I/O exception + */ + public static Model readToProvisioningModel(File file) throws IOException { + Model model = new Model(); + String featureName = StringUtils.substringBeforeLast(file.getName(), JsonOsgiConfigPostProcessor.FILE_EXTENSION); + Feature feature = model.getOrCreateFeature(featureName); + + Map data = readToMap(file); + for (Map.Entry entry : data.entrySet()) { + processEntry(feature, entry.getKey(), entry.getValue()); + } + + return model; + } + + /** + * Detect entries describing OSGi configurations and repoinit statements. + */ + @SuppressWarnings("unchecked") + private static void processEntry(Feature feature, String key, Object value) throws IOException { + Matcher configurationsKeyMatcher = KEY_PATTERN_CONFIGURATIONS.matcher(key); + if (configurationsKeyMatcher.matches()) { + if (value instanceof Map) { + String[] runModes = toRunModes(configurationsKeyMatcher.group(RUNMODES_INDEX)); + processOsgiConfiguration(feature, runModes, (Map)value); + } + else { + throw new IOException("Unexpected data for key " + key + ": " + value.getClass().getName()); + } + } + else { + Matcher repoinitKeyMatcher = KEY_PATTERN_REPOINIT.matcher(key); + if (repoinitKeyMatcher.matches()) { + if (value.getClass().isArray()) { + String[] runModes = toRunModes(repoinitKeyMatcher.group(RUNMODES_INDEX)); + processRepoInit(feature, runModes, (Object[])value); + } + else { + throw new IOException("Unexpected data for key " + key + ": " + value.getClass().getName()); + } + } + else { + throw new IOException("Invalid toplevel key in JSON file: " + key); + } + } + } + + private static String @Nullable [] toRunModes(String runModesString) { + if (StringUtils.isBlank(runModesString)) { + return null; + } + return StringUtils.split(runModesString, ","); + } + + /** + * Convert OSGi configurations to Provisioning model configurations with associated run modes. + */ + @SuppressWarnings("unchecked") + private static void processOsgiConfiguration(Feature feature, String[] runModes, Map configurations) throws IOException { + RunMode runMode = feature.getOrCreateRunMode(runModes); + for (Map.Entry entry : configurations.entrySet()) { + String pid = entry.getKey(); + Object value = entry.getValue(); + if (value instanceof Map) { + Map configProperties = (Map)value; + Configuration config = runMode.getOrCreateConfiguration(pid, null); + Dictionary properties = config.getProperties(); + for (Map.Entry configProperty : configProperties.entrySet()) { + properties.put(configProperty.getKey(), configProperty.getValue()); + } + } + else { + throw new IOException("Unexpected configurations data for " + pid + ": " + value.getClass().getName()); + } + } + } + + /** + * Convert repoinit statements to Provisioning model additional sections with associated run modes. + */ + private static void processRepoInit(Feature feature, String[] runModes, Object[] repoinits) { + Section section = new Section(ProvisioningUtil.REPOINIT_SECTION); + feature.getAdditionalSections().add(section); + if (runModes != null) { + section.getAttributes().put(ProvisioningUtil.REPOINIT_PROPERTY_RUNMODES, StringUtils.join(runModes, ",")); + } + section.setContents(StringUtils.join(repoinits, "\n")); + } + +} 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 80aeb32..0ddb467 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 @@ -20,13 +20,17 @@ package io.wcm.devops.conga.plugins.sling.util; import java.io.BufferedInputStream; +import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; +import java.util.Dictionary; import java.util.List; import java.util.Objects; import java.util.SortedSet; @@ -43,7 +47,9 @@ import org.apache.sling.provisioning.model.Section; import org.apache.sling.provisioning.model.io.ModelReader; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import io.wcm.devops.conga.generator.spi.context.FileContext; +import io.wcm.devops.conga.generator.spi.context.PostProcessorContext; import io.wcm.devops.conga.generator.util.FileUtil; /** @@ -61,7 +67,8 @@ public final class ProvisioningUtil { */ public static final String TEXT_FILE_EXTENSION = "txt"; - private static final String REPOINIT_SECTION = "repoinit"; + static final String REPOINIT_SECTION = "repoinit"; + static final String REPOINIT_PROPERTY_RUNMODES = "runModes"; private static final String REPOINIT_PID = "org.apache.sling.jcr.repoinit.RepositoryInitializer"; private ProvisioningUtil() { @@ -135,7 +142,7 @@ public static List visitOsgiConfigurations(Model model, ConfigConsumer } // associated run modes - String runModesString = section.getAttributes().get("runModes"); + String runModesString = section.getAttributes().get(REPOINIT_PROPERTY_RUNMODES); RunMode runMode; if (runModesString != null) { runMode = new RunMode(StringUtils.split(runModesString, ",")); @@ -184,4 +191,31 @@ private static String getPathForConfiguration(Configuration configuration, RunMo return path.toString(); } + /** + * Generate OSGi configuration for all feature and run modes. + * @param model Provisioning Model + * @param dir Target directory + * @param context Post processor context + * @return Generated files + * @throws IOException I/O exception + */ + public static List generateOsgiConfigurations(Model model, File dir, PostProcessorContext context) throws IOException { + return ProvisioningUtil.visitOsgiConfigurations(model, new ConfigConsumer() { + + @Override + @SuppressFBWarnings("RV_RETURN_VALUE_IGNORED_BAD_PRACTICE") + public FileContext accept(String path, Dictionary properties) throws IOException { + context.getLogger().info(" Generate {}", path); + + File confFile = new File(dir, path); + confFile.getParentFile().mkdirs(); + try (FileOutputStream os = new FileOutputStream(confFile)) { + OsgiConfigUtil.write(os, properties); + } + + return new FileContext().file(confFile).charset(StandardCharsets.UTF_8); + } + }); + } + } diff --git a/conga-sling-plugin/src/main/resources/META-INF/services/io.wcm.devops.conga.generator.spi.PostProcessorPlugin b/conga-sling-plugin/src/main/resources/META-INF/services/io.wcm.devops.conga.generator.spi.PostProcessorPlugin index 6d33cbb..890b2dd 100644 --- a/conga-sling-plugin/src/main/resources/META-INF/services/io.wcm.devops.conga.generator.spi.PostProcessorPlugin +++ b/conga-sling-plugin/src/main/resources/META-INF/services/io.wcm.devops.conga.generator.spi.PostProcessorPlugin @@ -1 +1,2 @@ +io.wcm.devops.conga.plugins.sling.postprocessor.JsonOsgiConfigPostProcessor io.wcm.devops.conga.plugins.sling.postprocessor.ProvisioningOsgiConfigPostProcessor \ No newline at end of file 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 new file mode 100644 index 0000000..089c4af --- /dev/null +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/postprocessor/JsonOsgiConfigPostProcessorTest.java @@ -0,0 +1,128 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 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.postprocessor; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Dictionary; + +import org.apache.commons.io.FileUtils; +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; +import org.slf4j.LoggerFactory; + +import io.wcm.devops.conga.generator.spi.PostProcessorPlugin; +import io.wcm.devops.conga.generator.spi.context.FileContext; +import io.wcm.devops.conga.generator.spi.context.PluginContextOptions; +import io.wcm.devops.conga.generator.spi.context.PostProcessorContext; +import io.wcm.devops.conga.generator.util.PluginManagerImpl; + +class JsonOsgiConfigPostProcessorTest { + + private PostProcessorPlugin underTest; + + private File targetDir; + + @BeforeEach + void setUp(TestInfo testInfo) throws IOException { + underTest = new PluginManagerImpl().get(JsonOsgiConfigPostProcessor.NAME, PostProcessorPlugin.class); + + // prepare target directory + targetDir = new File("target/JsonOsgiConfigPostProcessorTest_" + testInfo.getDisplayName()); + if (targetDir.exists()) { + FileUtils.deleteDirectory(targetDir); + } + } + + @Test + void testJsonFile() throws Exception { + + // post process example JSON file + File provisioningFile = new File(targetDir, "sample.osgiconfig.json"); + FileUtils.copyFile(new File(getClass().getResource("/osgi-config-json/sample.osgiconfig.json").toURI()), provisioningFile); + postProcess(provisioningFile); + + // validate generated configs + Dictionary config = readConfig("my.pid.cfg.json"); + assertEquals("value1", config.get("stringProperty")); + assertArrayEquals(new String[] { + "v1", "v2", "v3" + }, (String[])config.get("stringArrayProperty")); + assertEquals(true, config.get("booleanProperty")); + assertEquals(999999999999L, config.get("longProperty")); + + 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-sample.cfg.json"); + assertArrayEquals(new String[] { "create path /repoinit/test1\n" + + "create path /repoinit/test2" }, (String[])config.get("scripts")); + + config = readConfig("mode1/org.apache.sling.jcr.repoinit.RepositoryInitializer-sample-mode1.cfg.json"); + assertArrayEquals(new String[] { "create service user mode1" }, (String[])config.get("scripts")); + + config = readConfig("mode1.mode2/org.apache.sling.jcr.repoinit.RepositoryInitializer-sample-mode1-mode2.cfg.json"); + assertArrayEquals(new String[] { "create service user mode1_mode2" }, (String[])config.get("scripts")); + } + + private void postProcess(File provisioningFile) { + // post-process + FileContext fileContext = new FileContext() + .file(provisioningFile) + .charset(StandardCharsets.UTF_8); + PluginContextOptions pluginContextOptions = new PluginContextOptions() + .pluginManager(new PluginManagerImpl()) + .logger(LoggerFactory.getLogger(ProvisioningOsgiConfigPostProcessor.class)); + PostProcessorContext context = new PostProcessorContext() + .pluginContextOptions(pluginContextOptions); + + assertTrue(underTest.accepts(fileContext, context)); + underTest.apply(fileContext, context); + + // validate + assertFalse(provisioningFile.exists(), "Combined JSON file deleted"); + } + + private Dictionary readConfig(String fileName) throws IOException { + assertExists(fileName); + File file = new File(targetDir, fileName); + try (FileReader reader = new FileReader(file, StandardCharsets.UTF_8)) { + return Configurations.buildReader().build(reader).readConfiguration(); + } + } + + private void assertExists(String fileName) throws IOException { + File file = new File(targetDir, fileName); + assertTrue(file.exists(), "Config file found: " + file.getCanonicalPath()); + } + +} 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 1c0b609..34f4275 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 @@ -54,8 +54,8 @@ class ProvisioningOsgiConfigPostProcessorTest { void setUp(TestInfo testInfo) throws IOException { underTest = new PluginManagerImpl().get(ProvisioningOsgiConfigPostProcessor.NAME, PostProcessorPlugin.class); - // prepare target dirctory - targetDir = new File("target/postprocessor-test_" + testInfo.getDisplayName()); + // prepare target directory + targetDir = new File("target/ProvisioningOsgiConfigPostProcessorTest_" + testInfo.getDisplayName()); if (targetDir.exists()) { FileUtils.deleteDirectory(targetDir); } 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 new file mode 100644 index 0000000..3ba5384 --- /dev/null +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/util/JsonOsgiConfigUtilTest.java @@ -0,0 +1,49 @@ +/* + * #%L + * wcm.io + * %% + * Copyright (C) 2024 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 static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +import org.junit.jupiter.api.Test; + +class JsonOsgiConfigUtilTest { + + @Test + void testReadToMap() throws IOException { + Map content = JsonOsgiConfigUtil.readToMap(new File("src/test/resources/osgi-config-json/sample.osgiconfig.json")); + assertArrayEquals(new String[] { "create service user mode1" }, (String[])content.get("repoinit:mode1")); + } + + @Test + void testListToArrayConversion() throws IOException { + Map content = JsonOsgiConfigUtil.readToMap(new File("src/test/resources/arrayTypes.json")); + assertArrayEquals(new String[] { "v1", "v2", "v3" }, (String[])content.get("stringArray")); + assertArrayEquals(new Integer[] { 1, 2, 3 }, (Integer[])content.get("intArray")); + assertArrayEquals(new Boolean[] { true, false }, (Boolean[])content.get("boolArray")); + assertArrayEquals(new Object[] { "v1", 1, true }, (Object[])content.get("mixedArray")); + assertArrayEquals(new Object[0], (Object[])content.get("emptyArray")); + assertArrayEquals(new String[] { "v1" }, (String[])((Map)content.get("nested")).get("stringArray")); + } + +} diff --git a/conga-sling-plugin/src/test/resources/arrayTypes.json b/conga-sling-plugin/src/test/resources/arrayTypes.json new file mode 100644 index 0000000..dc6551a --- /dev/null +++ b/conga-sling-plugin/src/test/resources/arrayTypes.json @@ -0,0 +1,10 @@ +{ + "stringArray": ["v1","v2","v3"], + "intArray": [1,2,3], + "boolArray": [true,false], + "mixedArray": ["v1",1,true], + "emptyArray": [], + "nested": { + "stringArray": ["v1"] + } +} diff --git a/conga-sling-plugin/src/test/resources/osgi-config-json/sample.osgiconfig.json b/conga-sling-plugin/src/test/resources/osgi-config-json/sample.osgiconfig.json new file mode 100644 index 0000000..3bc24d6 --- /dev/null +++ b/conga-sling-plugin/src/test/resources/osgi-config-json/sample.osgiconfig.json @@ -0,0 +1,47 @@ +/* + * Example comment. + */ +{ + "configurations": { + "my.pid": { + "stringProperty": "value1", + "stringArrayProperty": ["v1","v2","v3"], + "booleanProperty": true, + "longProperty": 999999999999 + }, + "my.factory-my.pid": { + "stringProperty": "value2" + } + }, + + "configurations:mode1": { + "my.factory-my.pid2": { + "stringProperty": "value3" + } + }, + + "configurations:mode2": { + "my.pid2": { + "stringProperty": "value4" + } + }, + + "configurations:publish,prod": { + "my.pid2": { + "stringProperty": "value5" + } + }, + + "repoinit": [ + "create path /repoinit/test1", + "create path /repoinit/test2" + ], + + "repoinit:mode1": [ + "create service user mode1" + ], + + "repoinit:mode1,mode2": [ + "create service user mode1_mode2" + ] +} diff --git a/src/site/markdown/index.md b/src/site/markdown/index.md index fb34d85..6a12c9a 100644 --- a/src/site/markdown/index.md +++ b/src/site/markdown/index.md @@ -9,6 +9,7 @@ wcm.io DevOps CONGA Plugin for [Apache Sling][sling]. * [Usage][usage] * [CONGA Extensions][extensions] +* [Combined JSON file for defining OSGi Configurations][osgi-config-combined-json] * [API documentation][apidocs] * [Changelog][changelog] @@ -17,8 +18,8 @@ wcm.io DevOps CONGA Plugin for [Apache Sling][sling]. This plugin extends [CONGA][conga] with: -* Manage OSGi configuration templates in [Apache Sling Provisioning][sling-provisioning] file format -* Generate OSGi configurations in [Apache Felix Config Admin][felix-configadmin] file format +* Manage OSGi configuration templates in [Combined JSON files][osgi-config-combined-json] or [Apache Sling Provisioning][sling-provisioning] file format +* Generate OSGi configurations in `.cfg.json` files (as used by [Apache Sling Configuration Installer Factory][sling-configuration-installer-factory-cfg-json]) ### Further Resources @@ -31,9 +32,10 @@ This plugin extends [CONGA][conga] with: [usage]: usage.html [extensions]: extensions.html +[osgi-config-combined-json]: osgi-config-combined-json.html [apidocs]: conga-sling-plugin/apidocs/ [changelog]: changes-report.html [conga]: https://devops.wcm.io/conga/ [sling]: http://sling.apache.org/ [sling-provisioning]: https://sling.apache.org/documentation/development/slingstart.html -[felix-configadmin]: http://felix.apache.org/documentation/subprojects/apache-felix-config-admin.html +[sling-configuration-installer-factory-cfg-json]: https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#configuration-files-cfgjson diff --git a/src/site/markdown/osgi-config-combined-json.md b/src/site/markdown/osgi-config-combined-json.md new file mode 100644 index 0000000..f71585b --- /dev/null +++ b/src/site/markdown/osgi-config-combined-json.md @@ -0,0 +1,54 @@ +## Combined JSON file for defining OSGi Configurations + +Similar to the (deprecated) [Apache Sling Provisioning File Format][sling-provisioning] it is possible to define a set of OSGi configurations using a CONGA template in a single JSON file which contains a set of OSGi configurations and [repoinit][sling-repoinit] statements, optionally mapped to run modes. + +From this combined JSON file, CONGA generates individually `.cfg.json` files as used by [Apache Sling Configuration Installer Factory][sling-configuration-installer-factory-cfg-json]. With this approach it is easy to define all configurations in one file, and add/remove configurations based on CONGA environment variables using Handlebars logic. + +### JSON file example + +```json +{ + "configurations": { + "my.pid": { + "prop1": "value1", + "prop2": [1,2,3], + "prop3": true + } + }, + + "configurations:dev": { + "my.pid2": { + "prop1": "value-for-dev" + } + }, + + "configurations:publish,prod": { + "my.pid2": { + "prop1": "value-for-publish-prod" + } + } + + "repoinit": [ + "create path /repoinit/test1", + "create path /repoinit/test2" + ], + "repoinit:dev": [ + "create service user dev-user" + ] +} +``` + +The following keys are allowed on toplevel of the JSON file: + +* `configurations` - Configurations that are always active +* `configurations:runmode` - Configurations that are active only for the given run mode. +* `repoinit` - List of repoinit statements to be always applied +* `repoinit:runmode` - List of repoinit statements to be applied for the given run mode. + +`runmode` can be a comma-separate strings, e.g. `prod,publish`. In this case all given run modes have to be active. On AEM as a Cloud Service, only the [officially supported run modes][aemaacs-runmodes] are allowed. + + +[sling-provisioning]: https://sling.apache.org/documentation/development/slingstart.html +[sling-repoinit]: https://sling.apache.org/documentation/bundles/repository-initialization.html +[sling-configuration-installer-factory-cfg-json]: https://sling.apache.org/documentation/bundles/configuration-installer-factory.html#configuration-files-cfgjson +[aemaacs-runmodes]: https://experienceleague.adobe.com/docs/experience-manager-cloud-service/content/implementing/deploying/overview.html?lang=en#runmodes \ No newline at end of file From 4530ba7b6d2ef9be302cd55f5c7b5a3c7b8126e1 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 10 Jan 2024 13:25:21 +0100 Subject: [PATCH 07/16] reduce usage of Guava --- .../plugins/sling/fileheader/OsgiConfigFileHeaderTest.java | 6 ++---- .../sling/fileheader/ProvisioningFileHeaderTest.java | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeaderTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeaderTest.java index c17755d..046e9fd 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeaderTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeaderTest.java @@ -31,8 +31,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableList; - import io.wcm.devops.conga.generator.spi.FileHeaderPlugin; import io.wcm.devops.conga.generator.spi.context.FileContext; import io.wcm.devops.conga.generator.spi.context.FileHeaderContext; @@ -55,7 +53,7 @@ void testApply() throws Exception { File file = new File("target/generation-test/fileHeader.config"); FileUtils.write(file, "myscript", StandardCharsets.UTF_8); - List lines = ImmutableList.of("**********", "", "Der Jodelkaiser", "aus dem Oetztal", "ist wieder daheim.", "**********"); + List lines = List.of("**********", "", "Der Jodelkaiser", "aus dem Oetztal", "ist wieder daheim.", "**********"); FileHeaderContext context = new FileHeaderContext().commentLines(lines); FileContext fileContext = new FileContext().file(file); @@ -66,7 +64,7 @@ void testApply() throws Exception { "# Der Jodelkaiser aus dem Oetztal ist wieder daheim.")); FileHeaderContext extractContext = underTest.extract(fileContext); - assertEquals(ImmutableList.of("Der Jodelkaiser aus dem Oetztal ist wieder daheim."), extractContext.getCommentLines()); + assertEquals(List.of("Der Jodelkaiser aus dem Oetztal ist wieder daheim."), extractContext.getCommentLines()); file.delete(); } diff --git a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java index a2c0215..1649809 100644 --- a/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java +++ b/conga-sling-plugin/src/test/java/io/wcm/devops/conga/plugins/sling/fileheader/ProvisioningFileHeaderTest.java @@ -31,8 +31,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import com.google.common.collect.ImmutableList; - import io.wcm.devops.conga.generator.spi.FileHeaderPlugin; import io.wcm.devops.conga.generator.spi.context.FileContext; import io.wcm.devops.conga.generator.spi.context.FileHeaderContext; @@ -52,7 +50,7 @@ void testApply() throws Exception { File file = new File("target/generation-test/fileHeader.txt"); FileUtils.copyFile(new File(getClass().getResource("/provisioning/validProvisioning.txt").toURI()), file); - List lines = ImmutableList.of("Der Jodelkaiser", "aus dem Oetztal", "ist wieder daheim."); + List lines = List.of("Der Jodelkaiser", "aus dem Oetztal", "ist wieder daheim."); FileHeaderContext context = new FileHeaderContext().commentLines(lines); FileContext fileContext = new FileContext().file(file); From f1235bf3fd9bacad0c739ae0c711fc5698635718 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 10 Jan 2024 13:49:19 +0100 Subject: [PATCH 08/16] add link to real world example --- src/site/markdown/osgi-config-combined-json.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/site/markdown/osgi-config-combined-json.md b/src/site/markdown/osgi-config-combined-json.md index f71585b..e409481 100644 --- a/src/site/markdown/osgi-config-combined-json.md +++ b/src/site/markdown/osgi-config-combined-json.md @@ -47,6 +47,9 @@ The following keys are allowed on toplevel of the JSON file: `runmode` can be a comma-separate strings, e.g. `prod,publish`. In this case all given run modes have to be active. On AEM as a Cloud Service, only the [officially supported run modes][aemaacs-runmodes] are allowed. +You can use comments in the JSON file, they are stripped out when generating the `.cfg.json` files. + +Real world example with HBS template: [wcm-io-samples-aem-cms-config.osgiconfig.json.hbs](https://github.com/wcm-io/io.wcm.samples/blob/develop/config-definition/src/main/templates/wcm-io-samples-aem-cms/wcm-io-samples-aem-cms-config.osgiconfig.json.hbs) [sling-provisioning]: https://sling.apache.org/documentation/development/slingstart.html [sling-repoinit]: https://sling.apache.org/documentation/bundles/repository-initialization.html From 61655c1672254335c55d6d50ee9d866affbf81e6 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Wed, 10 Jan 2024 14:18:30 +0100 Subject: [PATCH 09/16] allow trailing commas in JSON objects and arrays --- .../plugins/sling/util/JsonOsgiConfigUtil.java | 10 +++++++--- .../plugins/sling/util/JsonOsgiConfigUtilTest.java | 3 ++- .../src/test/resources/arrayTypes.json | 13 +++++++------ src/site/markdown/osgi-config-combined-json.md | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) 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 bb01a63..825c3a2 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 @@ -39,7 +39,9 @@ import org.apache.sling.provisioning.model.Section; import org.jetbrains.annotations.Nullable; -import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.databind.type.MapType; import io.wcm.devops.conga.plugins.sling.postprocessor.JsonOsgiConfigPostProcessor; @@ -49,8 +51,10 @@ */ public final class JsonOsgiConfigUtil { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() - .enable(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_COMMENTS); + private static final JsonMapper OBJECT_MAPPER = JsonMapper.builder() + .enable(JsonParser.Feature.ALLOW_COMMENTS) + .enable(JsonReadFeature.ALLOW_TRAILING_COMMA) + .build(); 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(:(.*))?$"); 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 3ba5384..da65a32 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 @@ -41,7 +41,8 @@ void testListToArrayConversion() throws IOException { assertArrayEquals(new String[] { "v1", "v2", "v3" }, (String[])content.get("stringArray")); assertArrayEquals(new Integer[] { 1, 2, 3 }, (Integer[])content.get("intArray")); assertArrayEquals(new Boolean[] { true, false }, (Boolean[])content.get("boolArray")); - assertArrayEquals(new Object[] { "v1", 1, true }, (Object[])content.get("mixedArray")); + assertArrayEquals(new Boolean[] { null }, (Object[])content.get("nullArray")); + assertArrayEquals(new Object[] { "v1", 1, true, null }, (Object[])content.get("mixedArray")); assertArrayEquals(new Object[0], (Object[])content.get("emptyArray")); assertArrayEquals(new String[] { "v1" }, (String[])((Map)content.get("nested")).get("stringArray")); } diff --git a/conga-sling-plugin/src/test/resources/arrayTypes.json b/conga-sling-plugin/src/test/resources/arrayTypes.json index dc6551a..3be5a2b 100644 --- a/conga-sling-plugin/src/test/resources/arrayTypes.json +++ b/conga-sling-plugin/src/test/resources/arrayTypes.json @@ -1,10 +1,11 @@ { - "stringArray": ["v1","v2","v3"], - "intArray": [1,2,3], - "boolArray": [true,false], - "mixedArray": ["v1",1,true], + "stringArray": ["v1","v2","v3",], + "intArray": [1,2,3,], + "boolArray": [true,false,], + "nullArray": [null], + "mixedArray": ["v1",1,true,null], "emptyArray": [], "nested": { - "stringArray": ["v1"] - } + "stringArray": ["v1",], + }, } diff --git a/src/site/markdown/osgi-config-combined-json.md b/src/site/markdown/osgi-config-combined-json.md index e409481..6120e10 100644 --- a/src/site/markdown/osgi-config-combined-json.md +++ b/src/site/markdown/osgi-config-combined-json.md @@ -47,7 +47,7 @@ The following keys are allowed on toplevel of the JSON file: `runmode` can be a comma-separate strings, e.g. `prod,publish`. In this case all given run modes have to be active. On AEM as a Cloud Service, only the [officially supported run modes][aemaacs-runmodes] are allowed. -You can use comments in the JSON file, they are stripped out when generating the `.cfg.json` files. +You can use comments in the JSON file, they are stripped out when generating the `.cfg.json` files. It's also valid to use trailing commas in JSON arrays and JSON objects, this eases generating JSON via templates. Real world example with HBS template: [wcm-io-samples-aem-cms-config.osgiconfig.json.hbs](https://github.com/wcm-io/io.wcm.samples/blob/develop/config-definition/src/main/templates/wcm-io-samples-aem-cms/wcm-io-samples-aem-cms-config.osgiconfig.json.hbs) From 9dd1c26c267c9036cb9c517c0f72b0519db52fd6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jan 2024 16:07:43 +0000 Subject: [PATCH 10/16] Bump org.apache.felix:org.apache.felix.cm.json from 2.0.4 to 2.0.6 Bumps org.apache.felix:org.apache.felix.cm.json from 2.0.4 to 2.0.6. --- updated-dependencies: - dependency-name: org.apache.felix:org.apache.felix.cm.json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- conga-sling-plugin/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index 9b7917d..02eef94 100644 --- a/conga-sling-plugin/pom.xml +++ b/conga-sling-plugin/pom.xml @@ -89,7 +89,7 @@ org.apache.felix org.apache.felix.cm.json - 2.0.4 + 2.0.6 compile From d5591d5a77fed4ddd4ccf70ce17af9034b185d6d Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 18 Jan 2024 18:48:32 +0100 Subject: [PATCH 11/16] update parent dependency --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index c1125ba..ee35cba 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -25,7 +25,7 @@ io.wcm.devops io.wcm.devops.parent_toplevel - 1.4.2 + 1.4.3-SNAPSHOT From 6414ab571bc0ae61281fd178596d92487878d4e8 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Mon, 22 Jan 2024 10:24:35 +0100 Subject: [PATCH 12/16] eliminate usage of Guava --- conga-sling-plugin/pom.xml | 2 +- conga-sling-plugin/src/it/example/pom.xml | 2 +- .../conga/plugins/sling/fileheader/OsgiConfigFileHeader.java | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index 02eef94..59f3dff 100644 --- a/conga-sling-plugin/pom.xml +++ b/conga-sling-plugin/pom.xml @@ -60,7 +60,7 @@ io.wcm.devops.conga io.wcm.devops.conga.generator - 1.16.5-SNAPSHOT + 1.17.0-SNAPSHOT compile diff --git a/conga-sling-plugin/src/it/example/pom.xml b/conga-sling-plugin/src/it/example/pom.xml index 41fc0b0..8ce57db 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.5-SNAPSHOT + 1.17.0-SNAPSHOT true diff --git a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeader.java b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeader.java index df0a1fa..4932236 100644 --- a/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeader.java +++ b/conga-sling-plugin/src/main/java/io/wcm/devops/conga/plugins/sling/fileheader/OsgiConfigFileHeader.java @@ -20,13 +20,12 @@ package io.wcm.devops.conga.plugins.sling.fileheader; import java.io.IOException; +import java.util.Arrays; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; -import com.google.common.collect.ImmutableList; - import io.wcm.devops.conga.generator.GeneratorException; import io.wcm.devops.conga.generator.plugins.fileheader.AbstractFileHeader; import io.wcm.devops.conga.generator.spi.context.FileContext; @@ -95,7 +94,7 @@ public FileHeaderContext extract(FileContext file) { String[] contentLines = StringUtils.split(content, "\n"); if (contentLines.length > 0 && StringUtils.startsWith(contentLines[0], getCommentLinePrefix())) { String fullComment = StringUtils.trim(StringUtils.substringAfter(contentLines[0], getCommentBlockStart())); - List lines = ImmutableList.of(fullComment); + List lines = Arrays.asList(fullComment); return new FileHeaderContext().commentLines(lines); } } From a11e58519368f6992d15e6d7b1fd1084a2e2b693 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Mon, 22 Jan 2024 12:36:40 +0100 Subject: [PATCH 13/16] Eliminate SonarQube warnings --- .../io/wcm/devops/conga/plugins/sling/util/ProvisioningUtil.java | 1 + 1 file changed, 1 insertion(+) 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 0ddb467..4d1d164 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 @@ -117,6 +117,7 @@ public static Model getModel(FileContext file) throws IOException { * @return List of non-null results * @throws IOException I/O exception */ + @SuppressWarnings("java:S3776") // ignore complexity public static List visitOsgiConfigurations(Model model, ConfigConsumer consumer) throws IOException { List results = new ArrayList<>(); for (Feature feature : model.getFeatures()) { From a1cad5a01d5bb334e8e46f28ddd74c7e7480d250 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Thu, 25 Jan 2024 17:27:23 +0100 Subject: [PATCH 14/16] update parent dependency --- parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parent/pom.xml b/parent/pom.xml index ee35cba..cdbbbca 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -25,7 +25,7 @@ io.wcm.devops io.wcm.devops.parent_toplevel - 1.4.3-SNAPSHOT + 1.4.4 From d5356c729836be349e226b5f4bfdc3ef05888809 Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Fri, 26 Jan 2024 11:24:54 +0100 Subject: [PATCH 15/16] prepare release --- changes.xml | 2 +- conga-sling-plugin/pom.xml | 2 +- conga-sling-plugin/src/it/example/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/changes.xml b/changes.xml index 5eff9c0..6202d89 100644 --- a/changes.xml +++ b/changes.xml @@ -23,7 +23,7 @@ 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 JsonOsgiConfigPostProcessor to support reading a combined set of OSGi configuration for run modes from .osgiconfig.json files. diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index 59f3dff..825841b 100644 --- a/conga-sling-plugin/pom.xml +++ b/conga-sling-plugin/pom.xml @@ -60,7 +60,7 @@ io.wcm.devops.conga io.wcm.devops.conga.generator - 1.17.0-SNAPSHOT + 1.17.0 compile diff --git a/conga-sling-plugin/src/it/example/pom.xml b/conga-sling-plugin/src/it/example/pom.xml index 8ce57db..f041aab 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.17.0-SNAPSHOT + 1.17.0 true From 16671f1d182afe99b94176f1a2e98b55bbc0542e Mon Sep 17 00:00:00 2001 From: Stefan Seifert Date: Fri, 26 Jan 2024 11:25:38 +0100 Subject: [PATCH 16/16] [gitflow-maven-plugin] Update versions for release 1.4.0 --- conga-sling-plugin/pom.xml | 4 ++-- parent/pom.xml | 2 +- pom.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/conga-sling-plugin/pom.xml b/conga-sling-plugin/pom.xml index 825841b..e6f356c 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.4.0-SNAPSHOT + 1.4.0 ../parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling - 1.4.0-SNAPSHOT + 1.4.0 jar CONGA Sling Plugin diff --git a/parent/pom.xml b/parent/pom.xml index cdbbbca..a06ffa7 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.4.0-SNAPSHOT + 1.4.0 pom CONGA Sling Plugin diff --git a/pom.xml b/pom.xml index 83c7a6f..5ea1aba 100644 --- a/pom.xml +++ b/pom.xml @@ -23,13 +23,13 @@ io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.parent - 1.4.0-SNAPSHOT + 1.4.0 parent/pom.xml io.wcm.devops.conga.plugins io.wcm.devops.conga.plugins.sling.root - 1.4.0-SNAPSHOT + 1.4.0 pom CONGA Sling Plugin