SLING-5172 : Provide support for custom sections in the provisioning model
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1709592 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/provisioning/model/Feature.java b/src/main/java/org/apache/sling/provisioning/model/Feature.java
index 1d892ec..c78cb3e 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Feature.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Feature.java
@@ -32,6 +32,10 @@
extends Commentable
implements Comparable<Feature> {
+ /**
+ * The feature type
+ * @since 1.4.0
+ */
public enum Type {
PLAIN("plain"),
SUBSYSTEM_FEATURE("osgi.subsystem.feature"),
@@ -72,6 +76,9 @@
/** Feature name. */
private final String name;
+ /** Additional sections. */
+ private final List<Section> additionalSections = new ArrayList<Section>();
+
/**
* Construct a new feature.
* @param name The feature name
@@ -144,12 +151,48 @@
return result;
}
+ /**
+ * Get the feature type.
+ * @return The feature type.
+ * @since 1.4.0
+ */
public Type getType() {
return type;
}
- public void setType(Type t) {
- type = t;
+ /**
+ * Set the feature type.
+ * @param t The new type
+ * @since 1.4.0
+ */
+ public void setType(final Type t) {
+ type = ( t == null ? Type.PLAIN : t);
+ }
+
+ /**
+ * Get all additional sections
+ * @return The list of additional sections. It might be empty.
+ * @since 1.4.0
+ */
+ public List<Section> getAdditionalSections() {
+ return this.additionalSections;
+ }
+
+ /**
+ * Get all sections with the given name.
+ * @param name The section name.
+ * @return The list of sections. The list might be empty.
+ * @since 1.4.0
+ */
+ public List<Section> getAdditionalSections(final String name) {
+ final List<Section> result = new ArrayList<Section>();
+
+ for(final Section s : this.additionalSections) {
+ if ( name.equals(s.getName()) ) {
+ result.add(s);
+ }
+ }
+ return result;
}
@Override
@@ -171,6 +214,7 @@
return "Feature [runModes=" + runModes + ", variables=" + variables
+ ", name=" + name
+ ( type != Type.PLAIN ? ", type=" + type : "" )
+ + ( additionalSections.isEmpty() ? "" : ", additionalSections=" + this.additionalSections)
+ ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
+ "]";
}
diff --git a/src/main/java/org/apache/sling/provisioning/model/ModelProcessor.java b/src/main/java/org/apache/sling/provisioning/model/ModelProcessor.java
index ad4fcfc..58cad6d 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ModelProcessor.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ModelProcessor.java
@@ -23,10 +23,10 @@
/**
* Allows to process a value. A new value is created and for each part in the model a process
* method is called. Subclasses can overwrite those methods to inject specific behavior.
- * The processor itself does not change anything in the model.
+ * The processor itself does not change anything in the model.
*/
class ModelProcessor {
-
+
/**
* Creates a copy of the model and calls a process method for each part found in the model.
* This allows to modify the parts content (e.g. replace variables), but not to add or remove parts.
@@ -42,6 +42,7 @@
newFeature.setType(feature.getType());
newFeature.setComment(feature.getComment());
newFeature.setLocation(feature.getLocation());
+ newFeature.getAdditionalSections().addAll(feature.getAdditionalSections());
newFeature.getVariables().setComment(feature.getVariables().getComment());
newFeature.getVariables().setLocation(feature.getVariables().getLocation());
@@ -86,15 +87,15 @@
}
return result;
}
-
+
protected KeyValueMap<String> processVariables(KeyValueMap<String> variables, Feature newFeature) {
return variables;
}
-
+
protected Artifact processArtifact(Artifact artifact, Feature newFeature, RunMode newRunMode) {
return artifact;
}
-
+
protected Configuration processConfiguration(Configuration config, Feature newFeature, RunMode newRunMode) {
return config;
}
@@ -102,5 +103,5 @@
protected KeyValueMap<String> processSettings(KeyValueMap<String> settings, Feature newFeature, RunMode newRunMode) {
return settings;
}
-
+
}
diff --git a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
index 806975d..3a30ab9 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
@@ -53,6 +53,9 @@
final Feature baseFeature = base.getOrCreateFeature(feature.getName());
baseFeature.setType(feature.getType());
+ // additional sections
+ baseFeature.getAdditionalSections().addAll(feature.getAdditionalSections());
+
// variables
baseFeature.getVariables().putAll(feature.getVariables());
@@ -277,33 +280,33 @@
*/
String resolve(final Artifact artifact);
}
-
+
/**
* Parameter builder class for {@link ModelUtility#getEffectiveModel(Model, ResolverOptions)} method.
*/
public static final class ResolverOptions {
-
+
private VariableResolver variableResolver;
private ArtifactVersionResolver artifactVersionResolver;
-
+
public VariableResolver getVariableResolver() {
return variableResolver;
}
-
+
public ResolverOptions variableResolver(VariableResolver variableResolver) {
this.variableResolver = variableResolver;
return this;
}
-
+
public ArtifactVersionResolver getArtifactVersionResolver() {
return artifactVersionResolver;
}
-
+
public ResolverOptions artifactVersionResolver(ArtifactVersionResolver dependencyVersionResolver) {
this.artifactVersionResolver = dependencyVersionResolver;
return this;
}
-
+
}
/**
@@ -318,7 +321,7 @@
public static Model getEffectiveModel(final Model model, final VariableResolver resolver) {
return getEffectiveModel(model, new ResolverOptions().variableResolver(resolver));
}
-
+
/**
* Replace all variables in the model and return a new model with the replaced values.
* @param model The base model.
@@ -329,7 +332,7 @@
public static Model getEffectiveModel(final Model model) {
return getEffectiveModel(model, new ResolverOptions());
}
-
+
/**
* Replace all variables in the model and return a new model with the replaced values.
* @param model The base model.
@@ -417,7 +420,7 @@
}
return errors;
}
-
+
/**
* Applies a set of variables to the given model.
* All variables that are referenced anywhere within the model are detected and passed to the given variable resolver.
@@ -430,7 +433,7 @@
* @since 1.3
*/
public static Model applyVariables(final Model model, final VariableResolver resolver) {
-
+
// define delegating resolver that collects all variable names and value per feature
final Map<String,Map<String,String>> collectedVars = new HashMap<String, Map<String,String>>();
VariableResolver variableCollector = new VariableResolver() {
@@ -448,10 +451,10 @@
return value;
}
};
-
+
// use effective model processor to collect variables, but drop the resulting model
new EffectiveModelProcessor(new ResolverOptions().variableResolver(variableCollector)).process(model);
-
+
// define a processor that updates the "variables" sections in the features
ModelProcessor variablesUpdater = new ModelProcessor() {
@Override
@@ -466,7 +469,7 @@
return newVariables;
}
};
-
+
// return model with replaced "variables" sections
return variablesUpdater.process(model);
}
@@ -482,7 +485,7 @@
* @since 1.3
*/
public static Model applyArtifactVersions(final Model model, final ArtifactVersionResolver resolver) {
-
+
// define a processor that updates the versions of artifacts
ModelProcessor versionUpdater = new ModelProcessor() {
@Override
@@ -501,7 +504,7 @@
artifact.getType());
}
};
-
+
// return model with updated version artifacts
return versionUpdater.process(model);
}
diff --git a/src/main/java/org/apache/sling/provisioning/model/Section.java b/src/main/java/org/apache/sling/provisioning/model/Section.java
new file mode 100644
index 0000000..5c4ce38
--- /dev/null
+++ b/src/main/java/org/apache/sling/provisioning/model/Section.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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.
+ */
+package org.apache.sling.provisioning.model;
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+/**
+ * An additional section in the provisioning model.
+ * @since 1.4
+ */
+public class Section
+ extends Commentable {
+
+ /** The section name. */
+ private final String name;
+
+ /** Attributes. */
+ private final Map<String, String> attributes = new HashMap<String, String>();
+
+ /** Contents. */
+ private volatile String contents;
+
+ /**
+ * Construct a new feature.
+ * @param name The feature name
+ */
+ public Section(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Get the name of the section.
+ * @return The name or {@code null} for an anonymous feature.
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Get all attributes
+ * @return The map of attributes.
+ */
+ public Map<String, String> getAttributes() {
+ return this.attributes;
+ }
+
+ /**
+ * Get the contents of the section.
+ * @return The contents or {@code null}.
+ */
+ public String getContents() {
+ return contents;
+ }
+
+ /**
+ * Set the contents of the section.
+ * @param contents The new contents.
+ */
+ public void setContents(final String contents) {
+ this.contents = contents;
+ }
+
+ @Override
+ public String toString() {
+ return "Section [name=" + name
+ + ( attributes.isEmpty() ? "": ", attributes=" + attributes )
+ + ( contents == null ? "" : ", contents=" + contents)
+ + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
+ + "]";
+ }
+}
diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
index ec37e48..21ef564 100644
--- a/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
+++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelReader.java
@@ -31,6 +31,7 @@
import org.apache.sling.provisioning.model.Model;
import org.apache.sling.provisioning.model.ModelConstants;
import org.apache.sling.provisioning.model.RunMode;
+import org.apache.sling.provisioning.model.Section;
/**
* This class offers a method to read a model using a {@code Reader} instance.
@@ -44,7 +45,8 @@
ARTIFACTS("artifacts", new String[] {"runModes", "startLevel"}),
SETTINGS("settings", new String[] {"runModes"}),
CONFIGURATIONS("configurations", new String[] {"runModes"}),
- CONFIG(null, null);
+ CONFIG(null, null),
+ ADDITIONAL(null, null);
public final String name;
@@ -79,6 +81,8 @@
private ArtifactGroup artifactGroup;
private Configuration config;
+ private Section additionalSection;
+
private String comment;
private StringBuilder configBuilder;
@@ -109,6 +113,14 @@
// ignore empty line
if ( line.isEmpty() ) {
+ if ( this.mode == CATEGORY.ADDITIONAL ) {
+ if ( this.additionalSection.getContents() == null ) {
+ this.additionalSection.setContents(line);
+ } else {
+ this.additionalSection.setContents(this.additionalSection.getContents() + '\n' + line);
+ }
+ continue;
+ }
checkConfig();
continue;
}
@@ -121,6 +133,14 @@
continue;
}
+ if ( this.mode == CATEGORY.ADDITIONAL ) {
+ if ( this.additionalSection.getContents() == null ) {
+ this.additionalSection.setContents(line);
+ } else {
+ this.additionalSection.setContents(this.additionalSection.getContents() + '\n' + line);
+ }
+ continue;
+ }
final String c = line.substring(1).trim();
if ( comment == null ) {
comment = c;
@@ -138,6 +158,7 @@
}
if ( line.startsWith("[") ) {
+ additionalSection = null;
if ( !line.endsWith("]") ) {
throw new IOException(exceptionPrefix + "Illegal category definition in line " + this.lineNumberReader.getLineNumber() + ": " + line);
}
@@ -154,7 +175,8 @@
}
}
if ( found == null ) {
- throw new IOException(exceptionPrefix + "Unknown category in line " + this.lineNumberReader.getLineNumber() + ": " + line);
+ // additional section
+ found = CATEGORY.ADDITIONAL;
}
this.mode = found;
Map<String, String> parameters = Collections.emptyMap();
@@ -207,6 +229,13 @@
checkRunMode(parameters);
this.init(this.runMode.getConfigurations());
break;
+ case ADDITIONAL: checkFeature();
+ this.runMode = null;
+ this.artifactGroup = null;
+ this.additionalSection = new Section(category);
+ this.init(this.additionalSection);
+ this.feature.getAdditionalSections().add(this.additionalSection);
+ this.additionalSection.getAttributes().putAll(parameters);
}
} else {
switch ( this.mode ) {
@@ -288,6 +317,12 @@
case CONFIG : configBuilder.append(line);
configBuilder.append('\n');
break;
+ case ADDITIONAL : if ( this.additionalSection.getContents() == null ) {
+ this.additionalSection.setContents(line);
+ } else {
+ this.additionalSection.setContents(this.additionalSection.getContents() + '\n' + line);
+ }
+ break;
}
}
diff --git a/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java b/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
index d1fe473..e6160b3 100644
--- a/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
+++ b/src/main/java/org/apache/sling/provisioning/model/io/ModelWriter.java
@@ -33,6 +33,7 @@
import org.apache.sling.provisioning.model.Model;
import org.apache.sling.provisioning.model.ModelConstants;
import org.apache.sling.provisioning.model.RunMode;
+import org.apache.sling.provisioning.model.Section;
/**
* Simple writer for the a model
@@ -236,6 +237,23 @@
}
}
}
+
+ // additional sections
+ for(final Section section : feature.getAdditionalSections()) {
+ pw.print(" [");
+ pw.print(section.getName());
+ for(final Map.Entry<String, String> entry : section.getAttributes().entrySet()) {
+ pw.print(' ');
+ pw.print(entry.getKey());
+ pw.print('=');
+ pw.print(entry.getValue());
+ }
+ pw.println("]");
+ if ( section.getContents() != null ) {
+ pw.println(section.getContents());
+ }
+ pw.println();
+ }
}
}
}
diff --git a/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java b/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
index 5fa6a1e..bcfe275 100644
--- a/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
+++ b/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import java.io.StringReader;
@@ -27,6 +28,7 @@
import java.util.Map;
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.ModelUtility;
import org.apache.sling.provisioning.model.Traceable;
@@ -113,4 +115,12 @@
assertEquals("C", cfgC.getProperties().get("name"));
assertArrayEquals(new Integer[] {1,2,3}, (Integer[])cfgC.getProperties().get("array"));
}
+
+ @Test public void testAddition() throws Exception {
+ final Model model = U.readCompleteTestModel(new String[] {"additional.txt"});
+ final Feature f = model.getFeature("main");
+ assertNotNull(f);
+ assertEquals(1, f.getAdditionalSections().size());
+ assertEquals(1, f.getAdditionalSections("additional").size());
+ }
}
diff --git a/src/test/resources/additional.txt b/src/test/resources/additional.txt
new file mode 100644
index 0000000..b9b1524
--- /dev/null
+++ b/src/test/resources/additional.txt
@@ -0,0 +1,35 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+[feature name=main]
+
+[variables]
+ ws.version=1.0.2-from-main
+
+[artifacts]
+ commons-io/commons-io/1.4/jar
+
+[additional stuff=free]
+ # Hello
+ world
+
+ Hello
+
+[configurations]
+ org.apache.test.A
+ name="A"