SLING-9187: Allow to provide provisioning model name and runmodes via feature model folders
diff --git a/src/main/java/org/apache/sling/maven/slingstart/FeatureModelConverter.java b/src/main/java/org/apache/sling/maven/slingstart/FeatureModelConverter.java
index 0825f5f..bb0c564 100644
--- a/src/main/java/org/apache/sling/maven/slingstart/FeatureModelConverter.java
+++ b/src/main/java/org/apache/sling/maven/slingstart/FeatureModelConverter.java
@@ -27,7 +27,7 @@
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -81,6 +81,7 @@
public static class FeatureFileEntry {
public File file;
public String runModes;
+ public String model;
}
public static void convertDirectories(String featuresDirectory, MavenProject project, String defaultProvName,
@@ -92,16 +93,14 @@
}
}
- private static List<FeatureFileEntry> getFeatureFiles(final File baseDir, final String config) {
+ static List<FeatureFileEntry> getFeatureFiles(final File baseDir, final String config) {
final List<FeatureFileEntry> files = new ArrayList<>();
- for (final String cfg : config.split(",")) {
- final String[] directoryCfg = cfg.split("\\|");
- final String directory = directoryCfg[0].trim().replace('/', File.separatorChar);
+ for (final ParsedHeaderClause cfg : parseStandardHeader(config)) {
+ final String directory = cfg.m_paths.get(0).trim().replace('/', File.separatorChar);
- String runmodes = null;
- if (directoryCfg.length > 1) {
- runmodes = String.join(",", Arrays.copyOfRange(directoryCfg, 1, directoryCfg.length));
- }
+ String runmodes = (String) cfg.m_attrs.get(PROVISIONING_RUNMODES);
+ String model = (String) cfg.m_attrs.get(PROVISIONING_MODEL_NAME_VARIABLE);
+
final File featuresDir = new File(baseDir, directory);
final File[] children = featuresDir.listFiles();
if (children != null) {
@@ -110,6 +109,7 @@
final FeatureFileEntry ff = new FeatureFileEntry();
ff.file = f;
ff.runModes = runmodes;
+ ff.model = model;
files.add(ff);
}
}
@@ -154,7 +154,7 @@
String json = readFeatureFile(project, f, suggestedClassifier);
// check for prov model name
- if (defaultProvName != null || featureFile.runModes != null) {
+ if (defaultProvName != null || featureFile.runModes != null || featureFile.model != null) {
try (final Reader reader = new StringReader(json)) {
final Feature feature = FeatureJSONReader.read(reader, f.getAbsolutePath());
boolean update = false;
@@ -170,11 +170,20 @@
update = true;
}
- if (defaultProvName != null
- && feature.getVariables().get(PROVISIONING_MODEL_NAME_VARIABLE) == null) {
- feature.getVariables().put(PROVISIONING_MODEL_NAME_VARIABLE, defaultProvName);
- update = true;
+ if (feature.getVariables().get(PROVISIONING_MODEL_NAME_VARIABLE) == null) {
+ boolean updateInner = true;
+ if (featureFile.model != null) {
+ feature.getVariables().put(PROVISIONING_MODEL_NAME_VARIABLE, featureFile.model);
+ }
+ else if (defaultProvName != null) {
+ feature.getVariables().put(PROVISIONING_MODEL_NAME_VARIABLE, defaultProvName);
+ }
+ else {
+ updateInner = update;
+ }
+ update = updateInner;
}
+
if (update) {
try (final Writer writer = new StringWriter()) {
FeatureJSONWriter.write(writer, feature);
@@ -320,4 +329,187 @@
final MavenVersion mavenVersion = new MavenVersion(sb.toString());
return mavenVersion.getOSGiVersion().toString();
}
+
+ private static class ParsedHeaderClause
+ {
+ public final List<String> m_paths;
+ public final Map<String, String> m_dirs;
+ public final Map<String, Object> m_attrs;
+ public final Map<String, String> m_types;
+
+ public ParsedHeaderClause(
+ List<String> paths, Map<String, String> dirs, Map<String, Object> attrs,
+ Map<String, String> types)
+ {
+ m_paths = paths;
+ m_dirs = dirs;
+ m_attrs = attrs;
+ m_types = types;
+ }
+ }
+
+ private static final char EOF = (char) -1;
+
+ private static char charAt(int pos, String headers, int length)
+ {
+ if (pos >= length)
+ {
+ return EOF;
+ }
+ return headers.charAt(pos);
+ }
+
+ private static final int CLAUSE_START = 0;
+ private static final int PARAMETER_START = 1;
+ private static final int KEY = 2;
+ private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4;
+ private static final int ARGUMENT = 8;
+ private static final int VALUE = 16;
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ private static List<ParsedHeaderClause> parseStandardHeader(String header)
+ {
+ List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>();
+ if (header == null)
+ {
+ return clauses;
+ }
+ ParsedHeaderClause clause = null;
+ String key = null;
+ Map targetMap = null;
+ int state = CLAUSE_START;
+ int currentPosition = 0;
+ int startPosition = 0;
+ int length = header.length();
+ boolean quoted = false;
+ boolean escaped = false;
+
+ char currentChar = EOF;
+ do
+ {
+ currentChar = charAt(currentPosition, header, length);
+ switch (state)
+ {
+ case CLAUSE_START:
+ clause = new ParsedHeaderClause(
+ new ArrayList<String>(),
+ new HashMap<String, String>(),
+ new HashMap<String, Object>(),
+ new HashMap<String, String>());
+ clauses.add(clause);
+ state = PARAMETER_START;
+ case PARAMETER_START:
+ startPosition = currentPosition;
+ state = KEY;
+ case KEY:
+ switch (currentChar)
+ {
+ case ':':
+ case '=':
+ key = header.substring(startPosition, currentPosition).trim();
+ startPosition = currentPosition + 1;
+ targetMap = clause.m_attrs;
+ state = currentChar == ':' ? DIRECTIVE_OR_TYPEDATTRIBUTE : ARGUMENT;
+ break;
+ case EOF:
+ case ',':
+ case ';':
+ clause.m_paths.add(header.substring(startPosition, currentPosition).trim());
+ state = currentChar == ',' ? CLAUSE_START : PARAMETER_START;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ break;
+ case DIRECTIVE_OR_TYPEDATTRIBUTE:
+ switch(currentChar)
+ {
+ case '=':
+ if (startPosition != currentPosition)
+ {
+ clause.m_types.put(key, header.substring(startPosition, currentPosition).trim());
+ }
+ else
+ {
+ targetMap = clause.m_dirs;
+ }
+ state = ARGUMENT;
+ startPosition = currentPosition + 1;
+ break;
+ default:
+ break;
+ }
+ currentPosition++;
+ break;
+ case ARGUMENT:
+ if (currentChar == '\"')
+ {
+ quoted = true;
+ currentPosition++;
+ }
+ else
+ {
+ quoted = false;
+ }
+ if (!Character.isWhitespace(currentChar)) {
+ state = VALUE;
+ }
+ else {
+ currentPosition++;
+ }
+ break;
+ case VALUE:
+ if (escaped)
+ {
+ escaped = false;
+ }
+ else
+ {
+ if (currentChar == '\\' )
+ {
+ escaped = true;
+ }
+ else if (quoted && currentChar == '\"')
+ {
+ quoted = false;
+ }
+ else if (!quoted)
+ {
+ String value = null;
+ switch(currentChar)
+ {
+ case EOF:
+ case ';':
+ case ',':
+ value = header.substring(startPosition, currentPosition).trim();
+ if (value.startsWith("\"") && value.endsWith("\""))
+ {
+ value = value.substring(1, value.length() - 1);
+ }
+ if (targetMap.put(key, value) != null)
+ {
+ throw new IllegalArgumentException(
+ "Duplicate '" + key + "' in: " + header);
+ }
+ state = currentChar == ';' ? PARAMETER_START : CLAUSE_START;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ currentPosition++;
+ break;
+ default:
+ break;
+ }
+ } while ( currentChar != EOF);
+
+ if (state > PARAMETER_START)
+ {
+ throw new IllegalArgumentException("Unable to parse header: " + header);
+ }
+ return clauses;
+ }
}
diff --git a/src/test/java/org/apache/sling/maven/slingstart/FeatureModelConverterTest.java b/src/test/java/org/apache/sling/maven/slingstart/FeatureModelConverterTest.java
index 563ca62..ce78f1e 100644
--- a/src/test/java/org/apache/sling/maven/slingstart/FeatureModelConverterTest.java
+++ b/src/test/java/org/apache/sling/maven/slingstart/FeatureModelConverterTest.java
@@ -16,7 +16,9 @@
*/
package org.apache.sling.maven.slingstart;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
@@ -24,6 +26,7 @@
import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
+import java.util.List;
import java.util.Properties;
import org.apache.maven.artifact.repository.ArtifactRepository;
@@ -144,4 +147,37 @@
assertTrue(inheritsProv.contains("org.apache.aries/org.apache.aries.util/1.1.3"));
assertTrue(inheritsProv.contains("org.apache.sling/org.apache.sling.commons.log/5.1.0"));
}
+
+ @Test
+ public void testFeatureDirectoryDirectives() throws Exception {
+ List<FeatureModelConverter.FeatureFileEntry> featureFiles =
+ FeatureModelConverter.getFeatureFiles(new File(getClass().getResource("/features1").toURI()).getParentFile(),
+ "features1"
+ + "," + "features1"
+ + ";" + FeatureModelConverter.PROVISIONING_MODEL_NAME_VARIABLE + "=" + "quickstart"
+ + "," + "features1"
+ + ";" + FeatureModelConverter.PROVISIONING_MODEL_NAME_VARIABLE + "=" + "quickstart"
+ + ";" + FeatureModelConverter.PROVISIONING_RUNMODES + "=" + "author"
+ + "," + "features1"
+ + ";" + FeatureModelConverter.PROVISIONING_MODEL_NAME_VARIABLE + "=" + "samplecontent"
+ + "," + "features1"
+ + ";" + FeatureModelConverter.PROVISIONING_MODEL_NAME_VARIABLE + "=" + ":boot"
+ + ";" + FeatureModelConverter.PROVISIONING_RUNMODES + ":List<Integer>=" + "\"1,2,3\""
+ );
+
+ assertNull(featureFiles.get(0).model);
+ assertNull(featureFiles.get(0).runModes);
+
+ assertEquals("quickstart", featureFiles.get(1).model, "quickstart");
+ assertNull(featureFiles.get(1).runModes);
+
+ assertEquals("quickstart", featureFiles.get(2).model);
+ assertEquals("author", featureFiles.get(2).runModes);
+
+ assertEquals("samplecontent", featureFiles.get(3).model);
+ assertNull(featureFiles.get(3).runModes);
+
+ assertEquals(":boot", featureFiles.get(4).model);
+ assertEquals("1,2,3", featureFiles.get(4).runModes);
+ }
}