You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ro...@apache.org on 2017/11/07 09:57:18 UTC

[sling-org-apache-sling-provisioning-model] 22/34: Refactor model and add basic read/write test

This is an automated email from the ASF dual-hosted git repository.

rombert pushed a commit to annotated tag org.apache.sling.provisioning.model-1.0.0
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-provisioning-model.git

commit 4b98c32d19fc98161211fa6c411d12ee929c9acb
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Oct 1 16:03:47 2014 +0000

    Refactor model and add basic read/write test
    
    git-svn-id: https://svn.apache.org/repos/asf/sling/trunk/tooling/support/slingstart-model@1628749 13f79535-47bb-0310-9956-ffa450edef68
---
 pom.xml                                            |   4 +
 .../apache/sling/provisioning/model/Artifact.java  |   2 +-
 .../sling/provisioning/model/ArtifactGroup.java    |  17 +--
 .../model/{Traceable.java => Commentable.java}     |  26 +---
 .../sling/provisioning/model/Configuration.java    |   2 +-
 .../apache/sling/provisioning/model/Feature.java   |  16 +-
 .../apache/sling/provisioning/model/ItemList.java  |  50 +++++++
 .../sling/provisioning/model/KeyValueMap.java      |  55 +++++++
 .../org/apache/sling/provisioning/model/Model.java |   4 +-
 .../sling/provisioning/model/ModelUtility.java     |  20 +--
 .../apache/sling/provisioning/model/RunMode.java   |  18 ++-
 .../apache/sling/provisioning/model/Traceable.java |  25 +---
 .../sling/provisioning/model/io/ModelReader.java   | 166 +++++++++------------
 .../sling/provisioning/model/io/ModelWriter.java   |  86 +++++------
 .../apache/sling/provisioning/model/io/IOTest.java |  75 ++++++++++
 src/test/resources/boot.txt                        |  40 +++++
 src/test/resources/example.txt                     |  64 ++++++++
 src/test/resources/main.txt                        | 104 +++++++++++++
 src/test/resources/oak.txt                         |  19 +++
 19 files changed, 560 insertions(+), 233 deletions(-)

diff --git a/pom.xml b/pom.xml
index 46f9477..8d212a2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -64,5 +64,9 @@
             <version>1.2.8</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+        	<groupId>junit</groupId>
+        	<artifactId>junit</artifactId>
+        </dependency>
     </dependencies>
 </project>
diff --git a/src/main/java/org/apache/sling/provisioning/model/Artifact.java b/src/main/java/org/apache/sling/provisioning/model/Artifact.java
index bb5d844..432c4bf 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Artifact.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Artifact.java
@@ -25,7 +25,7 @@ import java.util.Map;
  * In addition, the classifier and type can be specified as well.
  * An artifact can have any metadata.
  */
-public class Artifact extends Traceable {
+public class Artifact extends Commentable {
 
     private final String groupId;
     private final String artifactId;
diff --git a/src/main/java/org/apache/sling/provisioning/model/ArtifactGroup.java b/src/main/java/org/apache/sling/provisioning/model/ArtifactGroup.java
index 0d0ccf9..8cb3550 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ArtifactGroup.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ArtifactGroup.java
@@ -16,20 +16,16 @@
  */
 package org.apache.sling.provisioning.model;
 
-import java.util.ArrayList;
-import java.util.List;
 
 /**
  * A artifact group holds a set of artifacts.
  * A valid start level is positive, start level 0 means the default OSGi start level.
  */
-public class ArtifactGroup extends Traceable
+public class ArtifactGroup extends ItemList<Artifact>
     implements Comparable<ArtifactGroup> {
 
     private final int level;
 
-    private final List<Artifact> artifacts = new ArrayList<Artifact>();
-
     public ArtifactGroup(final int level) {
         this.level = level;
     }
@@ -38,20 +34,17 @@ public class ArtifactGroup extends Traceable
         return this.level;
     }
 
-    public List<Artifact> getArtifacts() {
-        return this.artifacts;
-    }
-
     /**
      * Search an artifact with the same groupId, artifactId, version, type and classifier.
      * Version is not considered.
      */
     public Artifact search(final Artifact template) {
         Artifact found = null;
-        for(final Artifact current : this.artifacts) {
+        for(final Artifact current : this) {
             if ( current.getGroupId().equals(template.getGroupId())
               && current.getArtifactId().equals(template.getArtifactId())
-              && current.getClassifier().equals(template.getClassifier())
+              && ((current.getClassifier() == null && template.getClassifier() == null)
+                  || (current.getClassifier().equals(template.getClassifier()) ))
               && current.getType().equals(template.getType()) ) {
                 found = current;
                 break;
@@ -73,7 +66,7 @@ public class ArtifactGroup extends Traceable
     @Override
     public String toString() {
         return "ArtifactGroup [level=" + level
-                + ", artifacts=" + artifacts
+                + ", artifacts=" + this.items
                 + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
                 + "]";
     }
diff --git a/src/main/java/org/apache/sling/provisioning/model/Traceable.java b/src/main/java/org/apache/sling/provisioning/model/Commentable.java
similarity index 67%
copy from src/main/java/org/apache/sling/provisioning/model/Traceable.java
copy to src/main/java/org/apache/sling/provisioning/model/Commentable.java
index 4345ab5..b966a91 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Traceable.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Commentable.java
@@ -20,33 +20,12 @@ package org.apache.sling.provisioning.model;
  * A traceable has a comment and a location.
  * Both are optional.
  */
-public abstract class Traceable {
-
-    /** The location. */
-    private String location;
+public abstract class Commentable extends Traceable {
 
     /** The comment. */
     private String comment;
 
     /**
-     * Get the location.
-     * The location might be the location of the model file or any other
-     * means identifying where the object is defined.
-     * @return The location or {@code null}.
-     */
-    public String getLocation() {
-        return this.location;
-    }
-
-    /**
-     * Set the location.
-     * @param value The new location.
-     */
-    public void setLocation(final String value) {
-        this.location = value;
-    }
-
-    /**
      * Get the comment.
      * @return The comment or {@code null}.
      */
@@ -64,8 +43,7 @@ public abstract class Traceable {
 
     @Override
     public String toString() {
-        return "SSMTraceable [location=" + location + ", comment=" + comment
-                + "]";
+        return "Commentable [location=" + this.getLocation() + ", comment=" + comment + "]";
     }
 }
 
diff --git a/src/main/java/org/apache/sling/provisioning/model/Configuration.java b/src/main/java/org/apache/sling/provisioning/model/Configuration.java
index 67535ce..b317a14 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Configuration.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Configuration.java
@@ -23,7 +23,7 @@ import java.util.Hashtable;
 /**
  * Configuration
  */
-public class Configuration extends Traceable {
+public class Configuration extends Commentable {
 
     private final String pid;
 
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 480e1b8..f333888 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Feature.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Feature.java
@@ -19,9 +19,7 @@ package org.apache.sling.provisioning.model;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 
 
 /**
@@ -30,14 +28,14 @@ import java.util.Map;
  * - run modes
  */
 public class Feature
-    extends Traceable
+    extends Commentable
     implements Comparable<Feature> {
 
     /** All run modes. */
     private final List<RunMode> runModes = new ArrayList<RunMode>();
 
     /** Variables. */
-    private final Map<String, String> variables = new HashMap<String, String>();
+    private final KeyValueMap<String> variables = new KeyValueMap<String>();
 
     private final String name;
 
@@ -68,7 +66,7 @@ public class Feature
      * Get all variables
      * @return The set of variables
      */
-    public Map<String, String> getVariables() {
+    public KeyValueMap<String> getVariables() {
         return this.variables;
     }
 
@@ -122,4 +120,12 @@ public class Feature
         return this.name.compareTo(o.name);
     }
 
+    @Override
+    public String toString() {
+        return "Feature [runModes=" + runModes + ", variables=" + variables
+                + ", name=" + name
+                + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
+                + "]";
+    }
+
 }
diff --git a/src/main/java/org/apache/sling/provisioning/model/ItemList.java b/src/main/java/org/apache/sling/provisioning/model/ItemList.java
new file mode 100644
index 0000000..85ea138
--- /dev/null
+++ b/src/main/java/org/apache/sling/provisioning/model/ItemList.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ItemList<T>
+    extends Commentable
+    implements Iterable<T> {
+
+    protected final List<T> items = new ArrayList<T>();
+
+    public void add(final T item) {
+        this.items.add(item);
+    }
+
+    public void remove(final T item) {
+        this.items.remove(item);
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        return this.items.iterator();
+    }
+
+    public boolean isEmpty() {
+        return this.items.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return items.toString();
+    }
+}
diff --git a/src/main/java/org/apache/sling/provisioning/model/KeyValueMap.java b/src/main/java/org/apache/sling/provisioning/model/KeyValueMap.java
new file mode 100644
index 0000000..aa6598c
--- /dev/null
+++ b/src/main/java/org/apache/sling/provisioning/model/KeyValueMap.java
@@ -0,0 +1,55 @@
+/*
+ * 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.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class KeyValueMap<T>
+    extends Commentable
+    implements Iterable<Map.Entry<String, T>> {
+
+    private final Map<String, T> properties = new HashMap<String, T>();
+
+    public T get(final String key) {
+        return this.properties.get(key);
+    }
+
+    public void put(final String key, final T value) {
+        this.properties.put(key, value);
+    }
+
+    public void putAll(final KeyValueMap<T> map) {
+        this.properties.putAll(map.properties);
+    }
+
+    @Override
+    public Iterator<Entry<String, T>> iterator() {
+        return this.properties.entrySet().iterator();
+    }
+
+    public boolean isEmpty() {
+        return this.properties.isEmpty();
+    }
+
+    @Override
+    public String toString() {
+        return properties.toString();
+    }
+}
diff --git a/src/main/java/org/apache/sling/provisioning/model/Model.java b/src/main/java/org/apache/sling/provisioning/model/Model.java
index 5ce2a55..ee80611 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Model.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Model.java
@@ -69,8 +69,6 @@ public class Model extends Traceable {
 
     @Override
     public String toString() {
-        return "Model [features=" + features
-                + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
-                + "]";
+        return "Model [features=" + features + "]";
     }
 }
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 3188d1c..d42f727 100644
--- a/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
+++ b/src/main/java/org/apache/sling/provisioning/model/ModelUtility.java
@@ -55,12 +55,12 @@ public abstract class ModelUtility {
                 for(final ArtifactGroup group : runMode.getArtifactGroups()) {
                     final ArtifactGroup baseGroup = baseRunMode.getOrCreateArtifactGroup(group.getLevel());
 
-                    for(final Artifact artifact : group.getArtifacts()) {
+                    for(final Artifact artifact : group) {
                         final Artifact found = baseGroup.search(artifact);
                         if ( found != null ) {
-                            baseGroup.getArtifacts().remove(found);
+                            baseGroup.remove(found);
                         }
-                        baseGroup.getArtifacts().add(artifact);
+                        baseGroup.add(artifact);
                     }
                 }
 
@@ -75,7 +75,7 @@ public abstract class ModelUtility {
                 }
 
                 // settings
-                for(final Map.Entry<String, String> entry : runMode.getSettings().entrySet() ) {
+                for(final Map.Entry<String, String> entry : runMode.getSettings() ) {
                     baseRunMode.getSettings().put(entry.getKey(), entry.getValue());
                 }
             }
@@ -109,8 +109,6 @@ public abstract class ModelUtility {
      */
     public static Model getEffectiveModel(final Model model, final VariableResolver resolver) {
         final Model result = new Model();
-        result.setComment(model.getComment());
-        result.setLocation(model.getLocation());
 
         for(final Feature feature : model.getFeatures()) {
             final Feature newFeature = result.getOrCreateFeature(feature.getName());
@@ -121,15 +119,13 @@ public abstract class ModelUtility {
 
             for(final RunMode runMode : feature.getRunModes()) {
                 final RunMode newRunMode = newFeature.getOrCreateRunMode(runMode.getRunModes());
-                newRunMode.setComment(runMode.getComment());
-                newRunMode.setLocation(runMode.getLocation());
 
                 for(final ArtifactGroup group : runMode.getArtifactGroups()) {
                     final ArtifactGroup newGroup = newRunMode.getOrCreateArtifactGroup(group.getLevel());
                     newGroup.setComment(group.getComment());
                     newGroup.setLocation(group.getLocation());
 
-                    for(final Artifact artifact : group.getArtifacts()) {
+                    for(final Artifact artifact : group) {
                         final Artifact newArtifact = new Artifact(replace(feature, artifact.getGroupId(), resolver),
                                 replace(feature, artifact.getArtifactId(), resolver),
                                 replace(feature, artifact.getVersion(), resolver),
@@ -138,7 +134,7 @@ public abstract class ModelUtility {
                         newArtifact.setComment(artifact.getComment());
                         newArtifact.setLocation(artifact.getLocation());
 
-                        newGroup.getArtifacts().add(newArtifact);
+                        newGroup.add(newArtifact);
                     }
                 }
 
@@ -201,7 +197,7 @@ public abstract class ModelUtility {
                     newRunMode.getConfigurations().add(newConfig);
                 }
 
-                for(final Map.Entry<String, String> entry : runMode.getSettings().entrySet() ) {
+                for(final Map.Entry<String, String> entry : runMode.getSettings() ) {
                     newRunMode.getSettings().put(entry.getKey(), replace(feature, entry.getValue(), resolver));
                 }
             }
@@ -284,7 +280,7 @@ public abstract class ModelUtility {
                     if ( sl.getLevel() < 0 ) {
                         errors.put(sl, "Invalid start level " + sl.getLevel());
                     }
-                    for(final Artifact a : sl.getArtifacts()) {
+                    for(final Artifact a : sl) {
                         String error = null;
                         if ( a.getGroupId() == null || a.getGroupId().isEmpty() ) {
                             error = "groupId missing";
diff --git a/src/main/java/org/apache/sling/provisioning/model/RunMode.java b/src/main/java/org/apache/sling/provisioning/model/RunMode.java
index 677abaf..72a8b23 100644
--- a/src/main/java/org/apache/sling/provisioning/model/RunMode.java
+++ b/src/main/java/org/apache/sling/provisioning/model/RunMode.java
@@ -19,9 +19,7 @@ package org.apache.sling.provisioning.model;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -43,9 +41,9 @@ public class RunMode
 
     private final List<ArtifactGroup> groups = new ArrayList<ArtifactGroup>();
 
-    private final List<Configuration> configurations = new ArrayList<Configuration>();
+    private final ItemList<Configuration> configurations = new ItemList<Configuration>();
 
-    private final Map<String, String> settings = new HashMap<String, String>();
+    private final KeyValueMap<String> settings = new KeyValueMap<String>();
 
     public RunMode(final String[] runModes) {
         this.runModes = getSortedRunModesArray(runModes);
@@ -153,7 +151,7 @@ public class RunMode
         return null;
     }
 
-    public Configuration getOrCreateConfiguration(final String pid, final String factoryPid) {
+    public Configuration getConfiguration(final String pid, final String factoryPid) {
         Configuration found = null;
         for(final Configuration current : this.configurations) {
             if ( factoryPid == null ) {
@@ -168,6 +166,11 @@ public class RunMode
                 }
             }
         }
+        return found;
+    }
+
+    public Configuration getOrCreateConfiguration(final String pid, final String factoryPid) {
+        Configuration found = getConfiguration(pid, factoryPid);
         if ( found == null ) {
             found = new Configuration(pid, factoryPid);
             this.configurations.add(found);
@@ -179,11 +182,11 @@ public class RunMode
         return this.groups;
     }
 
-    public List<Configuration> getConfigurations() {
+    public ItemList<Configuration> getConfigurations() {
         return this.configurations;
     }
 
-    public Map<String, String> getSettings() {
+    public KeyValueMap<String> getSettings() {
         return this.settings;
     }
 
@@ -209,7 +212,6 @@ public class RunMode
         return "RunMode [runModes=" + Arrays.toString(runModes) + ", groups="
                 + groups + ", configurations=" + configurations + ", settings="
                 + settings
-                + ( this.getLocation() != null ? ", location=" + this.getLocation() : "")
                 + "]";
     }
 
diff --git a/src/main/java/org/apache/sling/provisioning/model/Traceable.java b/src/main/java/org/apache/sling/provisioning/model/Traceable.java
index 4345ab5..d4ced41 100644
--- a/src/main/java/org/apache/sling/provisioning/model/Traceable.java
+++ b/src/main/java/org/apache/sling/provisioning/model/Traceable.java
@@ -17,17 +17,13 @@
 package org.apache.sling.provisioning.model;
 
 /**
- * A traceable has a comment and a location.
- * Both are optional.
+ * A traceable has an optional location.
  */
 public abstract class Traceable {
 
     /** The location. */
     private String location;
 
-    /** The comment. */
-    private String comment;
-
     /**
      * Get the location.
      * The location might be the location of the model file or any other
@@ -46,26 +42,9 @@ public abstract class Traceable {
         this.location = value;
     }
 
-    /**
-     * Get the comment.
-     * @return The comment or {@code null}.
-     */
-    public String getComment() {
-        return this.comment;
-    }
-
-    /**
-     * Set the comment.
-     * @param value The new comment.
-     */
-    public void setComment(final String value) {
-        this.comment = value;
-    }
-
     @Override
     public String toString() {
-        return "SSMTraceable [location=" + location + ", comment=" + comment
-                + "]";
+        return "Traceable [location=" + location + "]";
     }
 }
 
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 c3833df..948f3e6 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
@@ -25,31 +25,32 @@ import java.util.Map;
 
 import org.apache.sling.provisioning.model.Artifact;
 import org.apache.sling.provisioning.model.ArtifactGroup;
+import org.apache.sling.provisioning.model.Commentable;
 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.ModelConstants;
 import org.apache.sling.provisioning.model.RunMode;
-import org.apache.sling.provisioning.model.Traceable;
 
 
 public class ModelReader {
 
     private enum CATEGORY {
-        NONE(null),
-        FEATURE("feature"),
-        VARIABLES("variables"),
-        GLOBAL("global"),
-        RUN_MODE("runMode"),
-        ARTIFACTS("artifacts"),
-        SETTINGS("settings"),
-        CONFIGURATIONS("configurations"),
-        CONFIG(null);
+        NONE(null, null),
+        FEATURE("feature", new String[] {"name"}),
+        VARIABLES("variables", null),
+        ARTIFACTS("artifacts", new String[] {"runModes", "startLevel"}),
+        SETTINGS("settings", new String[] {"runModes"}),
+        CONFIGURATIONS("configurations", new String[] {"runModes"}),
+        CONFIG(null, null);
 
         public final String name;
 
-        private CATEGORY(final String n) {
+        public final String[] parameters;
+
+        private CATEGORY(final String n, final String[] p) {
             this.name = n;
+            this.parameters = p;
         }
     }
 
@@ -64,9 +65,6 @@ public class ModelReader {
         return mr.readModel(reader);
     }
 
-    /** Is this a single feature model? */
-    private boolean isSingleFeature = false;
-
     private CATEGORY mode = CATEGORY.NONE;
 
     private final Model model = new Model();
@@ -101,16 +99,18 @@ public class ModelReader {
         lineNumberReader = new LineNumberReader(reader);
         String line;
         while ( (line = lineNumberReader.readLine()) != null ) {
+            // trim the line
             line = line.trim();
+
             // ignore empty line
             if ( line.isEmpty() ) {
                 checkConfig();
                 continue;
             }
+
             // comment?
             if ( line.startsWith("#") ) {
                 checkConfig();
-                mode = CATEGORY.NONE;
                 final String c = line.substring(1).trim();
                 if ( comment == null ) {
                     comment = c;
@@ -122,8 +122,9 @@ public class ModelReader {
 
             if ( global ) {
                 global = false;
-                model.setComment(comment);
-                comment = null;
+                if ( !line.startsWith("[feature ") ) {
+                    throw new IOException(exceptionPrefix + " Model file must start with a feature category.");
+                }
             }
 
             if ( line.startsWith("[") ) {
@@ -149,22 +150,16 @@ public class ModelReader {
                 Map<String, String> parameters = Collections.emptyMap();
                 if (line.charAt(pos) != ']') {
                     final String parameterLine = line.substring(pos + 1, line.length() - 1).trim();
-                    parameters = parseParameters(parameterLine);
+                    parameters = parseParameters(parameterLine, this.mode.parameters);
                 }
 
                 switch ( this.mode ) {
                     case NONE : break; // this can never happen
                     case CONFIG : break; // this can never happen
-                    case FEATURE : if ( this.isSingleFeature ) {
-                                       throw new IOException(exceptionPrefix + "Single feature model allows only one feature.");
-                                   }
-                                   final String name = parameters.get("name");
+                    case FEATURE : final String name = parameters.get("name");
                                    if ( name == null ) {
                                        throw new IOException(exceptionPrefix + "Feature name missing in line " + this.lineNumberReader.getLineNumber() + ": " + line);
                                    }
-                                   if ( parameters.size() > 1 ) {
-                                       throw new IOException(exceptionPrefix + "Unknown feature parameters in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                   }
                                    if ( model.findFeature(name) != null ) {
                                        throw new IOException(exceptionPrefix + "Duplicate feature in line " + this.lineNumberReader.getLineNumber() + ": " + line);
                                    }
@@ -174,45 +169,16 @@ public class ModelReader {
                                    this.artifactGroup = null;
                                    break;
                     case VARIABLES : checkFeature();
+                                     this.init(this.feature.getVariables());
                                      break;
-                    case RUN_MODE : checkFeature();
-                                    final String names = parameters.get("names");
-                                    if ( names == null ) {
-                                        throw new IOException(exceptionPrefix + "Run mode names missing in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                    }
-                                    if ( parameters.size() > 1 ) {
-                                        throw new IOException(exceptionPrefix + "Unknown run mode parameters in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                    }
-                                    final String[] rm = names.split(",");
-                                    if ( this.feature.getRunMode(rm) != null ) {
-                                        throw new IOException(exceptionPrefix + "Duplicate run mode in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                    }
-                                    this.runMode = this.feature.getOrCreateRunMode(rm);
-                                    this.init(this.runMode);
-                                    this.artifactGroup = null;
-                                    break;
-                    case GLOBAL : checkFeature();
-                                  if ( !parameters.isEmpty() ) {
-                                      throw new IOException(exceptionPrefix + "Unknown global parameters in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                  }
-                                  if ( this.feature.getRunMode(null) != null ) {
-                                      throw new IOException(exceptionPrefix + "Duplicate global run mode in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                  }
-                                  this.runMode = this.feature.getOrCreateRunMode(null);
-                                  this.init(this.runMode);
-                                  this.artifactGroup = null;
-                                  break;
                     case SETTINGS: checkFeature();
-                                   checkRunMode();
+                                   checkRunMode(parameters);
+                                   this.init(this.runMode.getSettings());
                                    break;
                     case ARTIFACTS: checkFeature();
-                                    checkRunMode();
-                                    String level = parameters.get("startLevel");
-                                    if ( (level == null && !parameters.isEmpty())
-                                        || (level != null && parameters.size() > 1 ) ) {
-                                        throw new IOException(exceptionPrefix + "Unknown artifacts parameters in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                    }
+                                    checkRunMode(parameters);
                                     int startLevel = 0;
+                                    String level = parameters.get("startLevel");
                                     if ( level != null ) {
                                         try {
                                             startLevel = Integer.valueOf(level);
@@ -227,7 +193,8 @@ public class ModelReader {
                                     this.init(this.artifactGroup);
                                     break;
                     case CONFIGURATIONS: checkFeature();
-                                         checkRunMode();
+                                         checkRunMode(parameters);
+                                         this.init(this.runMode.getConfigurations());
                                          break;
                 }
             } else {
@@ -239,27 +206,22 @@ public class ModelReader {
                     case SETTINGS : final String[] settings = parseProperty(line);
                                     runMode.getSettings().put(settings[0], settings[1]);
                                     break;
-                    case FEATURE:
-                    case RUN_MODE:
-                    case GLOBAL:
-                    case ARTIFACTS : this.checkFeature();
-                                     this.checkRunMode();
-                                     if ( this.artifactGroup == null ) {
-                                         this.artifactGroup = this.runMode.getOrCreateArtifactGroup(0);
-                                     }
-                                     String artifactUrl = line;
+                    case FEATURE:   this.runMode = this.feature.getOrCreateRunMode(null);
+                                    this.artifactGroup = this.runMode.getOrCreateArtifactGroup(0);
+                                    // no break, we continue with ARTIFACT
+                    case ARTIFACTS : String artifactUrl = line;
                                      Map<String, String> parameters = Collections.emptyMap();
                                      if ( line.endsWith("]") ) {
                                          final int startPos = line.indexOf("[");
                                          if ( startPos != -1 ) {
                                              artifactUrl = line.substring(0, startPos).trim();
-                                             parameters = parseParameters(line.substring(startPos + 1, line.length() - 1).trim());
+                                             parameters = parseParameters(line.substring(startPos + 1, line.length() - 1).trim(), null);
                                          }
                                      }
                                      try {
                                          final Artifact artifact = Artifact.fromMvnUrl("mvn:" + artifactUrl);
                                          this.init(artifact);
-                                         this.artifactGroup.getArtifacts().add(artifact);
+                                         this.artifactGroup.add(artifact);
                                          artifact.getMetadata().putAll(parameters);
                                      } catch ( final IllegalArgumentException iae) {
                                          throw new IOException(exceptionPrefix + iae.getMessage() + " in line " + this.lineNumberReader.getLineNumber(), iae);
@@ -271,14 +233,10 @@ public class ModelReader {
                                               final int startPos = line.indexOf("[");
                                               if ( startPos != -1 ) {
                                                   configId = line.substring(0, startPos).trim();
-                                                  cfgPars = parseParameters(line.substring(startPos + 1, line.length() - 1).trim());
+                                                  cfgPars = parseParameters(line.substring(startPos + 1, line.length() - 1).trim(), new String[] {"format"});
                                               }
                                           }
                                           String format = cfgPars.get("format");
-                                          if ( (format == null && !cfgPars.isEmpty())
-                                               || (format != null && cfgPars.size() > 1 ) ) {
-                                              throw new IOException(exceptionPrefix + "Unknown configuration parameters in line " + this.lineNumberReader.getLineNumber() + ": " + line);
-                                          }
                                           if ( format != null ) {
                                               if ( !ModelConstants.CFG_FORMAT_FELIX_CA.equals(format)
                                                   && !ModelConstants.CFG_FORMAT_PROPERTIES.equals(format) ) {
@@ -287,15 +245,23 @@ public class ModelReader {
                                           } else {
                                               format = ModelConstants.CFG_FORMAT_FELIX_CA;
                                           }
+                                          final String pid;
+                                          final String factoryPid;
                                           final int factoryPos = configId.indexOf('-');
                                           if ( factoryPos == -1 ) {
+                                              pid = configId;
+                                              factoryPid = null;
                                               config = new Configuration(configId, null);
                                           } else {
-                                              config = new Configuration(configId.substring(factoryPos + 1), configId.substring(0, factoryPos));
+                                              pid = configId.substring(factoryPos + 1);
+                                              factoryPid = configId.substring(0, factoryPos);
+                                          }
+                                          if ( runMode.getConfiguration(pid, factoryPid) != null ) {
+                                              throw new IOException(exceptionPrefix + "Duplicate configuration in line " + this.lineNumberReader.getLineNumber());
                                           }
+                                          config = runMode.getOrCreateConfiguration(pid, factoryPid);
                                           this.init(config);
                                           config.getProperties().put(ModelConstants.CFG_UNPROCESSED_FORMAT, format);
-                                          runMode.getConfigurations().add(config);
                                           configBuilder = new StringBuilder();
                                           mode = CATEGORY.CONFIG;
                                           break;
@@ -318,30 +284,26 @@ public class ModelReader {
      */
     private void checkFeature() throws IOException {
         if ( feature == null ) {
-            if ( model.getLocation() == null ) {
-                throw new IOException(exceptionPrefix + "No preceding feature definition in line " + this.lineNumberReader.getLineNumber());
-            }
-            final int beginPos = model.getLocation().replace('\\', '/').lastIndexOf("/");
-            String newName = model.getLocation().substring(beginPos + 1);
-            final int endPos = newName.lastIndexOf('.');
-            if ( endPos != -1 ) {
-                newName = newName.substring(0, endPos);
-            }
-            this.isSingleFeature = true;
-            feature = model.getOrCreateFeature(newName);
+            throw new IOException(exceptionPrefix + "No preceding feature definition in line " + this.lineNumberReader.getLineNumber());
         }
     }
 
     /**
      * Check for a run mode object
      */
-    private void checkRunMode() throws IOException {
-        if ( runMode == null ) {
-            runMode = this.feature.getOrCreateRunMode(null);
+    private void checkRunMode(final Map<String, String> parameters) throws IOException {
+        String[] runModes = null;
+        final String rmDef = parameters.get("runModes");
+        if ( rmDef != null ) {
+            runModes = rmDef.split(",");
+            for(int i=0; i<runModes.length; i++) {
+                runModes[i] = runModes[i].trim();
+            }
         }
+        runMode = this.feature.getOrCreateRunMode(runModes);
     }
 
-    private void init(final Traceable traceable) {
+    private void init(final Commentable traceable) {
         traceable.setComment(this.comment);
         this.comment = null;
         final String number = String.valueOf(this.lineNumberReader.getLineNumber());
@@ -377,7 +339,7 @@ public class ModelReader {
         return new String[] {key, value};
     }
 
-    private Map<String, String> parseParameters(final String line) throws IOException {
+    private Map<String, String> parseParameters(final String line, final String[] allowedParameters) throws IOException {
         final Map<String, String>parameters = new HashMap<String, String>();
         final String[] keyValuePairs = line.split(" ");
         for(String kv : keyValuePairs) {
@@ -387,7 +349,21 @@ public class ModelReader {
                 if ( sep == -1 ) {
                     throw new IOException(exceptionPrefix + "Invalid parameter definition in line " + this.lineNumberReader.getLineNumber() + ": " + line);
                 }
-                parameters.put(kv.substring(0, sep).trim(), kv.substring(sep + 1).trim());
+                final String key = kv.substring(0, sep).trim();
+                parameters.put(key, kv.substring(sep + 1).trim());
+
+                if ( allowedParameters != null ) {
+                    boolean found = false;
+                    for(final String allowed : allowedParameters) {
+                        if ( key.equals(allowed) ) {
+                            found = true;
+                            break;
+                        }
+                    }
+                    if ( !found ) {
+                        throw new IOException(exceptionPrefix + "Invalid parameter " + key + " in line " + this.lineNumberReader.getLineNumber());
+                    }
+                }
             }
         }
         return parameters;
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 4b023f0..09c8423 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
@@ -27,22 +27,22 @@ import java.util.Map;
 import org.apache.felix.cm.file.ConfigurationHandler;
 import org.apache.sling.provisioning.model.Artifact;
 import org.apache.sling.provisioning.model.ArtifactGroup;
+import org.apache.sling.provisioning.model.Commentable;
 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.ModelConstants;
 import org.apache.sling.provisioning.model.RunMode;
-import org.apache.sling.provisioning.model.Traceable;
 
 /**
  * Simple writer for the a model
  */
 public class ModelWriter {
 
-    private static void writeComment(final PrintWriter pw, final Traceable traceable)
+    private static void writeComment(final PrintWriter pw, final Commentable commentable)
     throws IOException {
-        if ( traceable.getComment() != null ) {
-            final LineNumberReader lnr = new LineNumberReader(new StringReader(traceable.getComment()));
+        if ( commentable.getComment() != null ) {
+            final LineNumberReader lnr = new LineNumberReader(new StringReader(commentable.getComment()));
             try {
                 String line = null;
                 while ( (line = lnr.readLine()) != null ) {
@@ -55,6 +55,22 @@ public class ModelWriter {
         }
     }
 
+    private static void writeRunMode(final PrintWriter pw, final RunMode runMode) {
+        final String[] rm = runMode.getRunModes();
+        if ( rm != null && rm.length > 0 ) {
+            pw.print(" runModes=");
+            boolean first = true;
+            for(final String mode : rm) {
+                if ( first ) {
+                    first = false;
+                } else {
+                    pw.print(",");
+                }
+                pw.print(mode);
+            }
+        }
+    }
+
     /**
      * Writes the model to the writer.
      * The writer is not closed.
@@ -66,8 +82,6 @@ public class ModelWriter {
     throws IOException {
         final PrintWriter pw = new PrintWriter(writer);
 
-        writeComment(pw, model);
-
         // features
         for(final Feature feature : model.getFeatures()) {
             writeComment(pw, feature);
@@ -78,8 +92,9 @@ public class ModelWriter {
 
             // variables
             if ( !feature.getVariables().isEmpty() ) {
+                writeComment(pw, feature.getVariables());
                 pw.println("[variables]");
-                for(final Map.Entry<String, String> entry : feature.getVariables().entrySet()) {
+                for(final Map.Entry<String, String> entry : feature.getVariables()) {
                     pw.print("  ");
                     pw.print(entry.getKey());
                     pw.print("=");
@@ -90,43 +105,14 @@ public class ModelWriter {
 
             // run modes
             for(final RunMode runMode : feature.getRunModes()) {
-                // skip empty run mode
-                if ( runMode.getConfigurations().isEmpty() && runMode.getSettings().isEmpty() ) {
-                    boolean hasArtifacts = false;
-                    for(final ArtifactGroup sl : runMode.getArtifactGroups()) {
-                        if ( !sl.getArtifacts().isEmpty() ) {
-                            hasArtifacts = true;
-                            break;
-                        }
-                    }
-                    if ( !hasArtifacts ) {
-                        continue;
-                    }
-                }
-                writeComment(pw, runMode);
-                final String[] runModes = runMode.getRunModes();
-                if ( runModes == null || runModes.length == 0 ) {
-                    pw.println("[global]");
-                } else {
-                    pw.print("[runMode names=");
-                    boolean first = true;
-                    for(final String mode : runModes) {
-                        if ( first ) {
-                            first = false;
-                        } else {
-                            pw.print(",");
-                        }
-                        pw.print(mode);
-                    }
-                    pw.println("]");
-                }
-                pw.println();
-
                 // settings
                 if ( !runMode.getSettings().isEmpty() ) {
-                    pw.println("[settings]");
+                    writeComment(pw, runMode.getSettings());
+                    pw.print("[settings");
+                    writeRunMode(pw, runMode);
+                    pw.println("]");
 
-                    for(final Map.Entry<String, String> entry : runMode.getSettings().entrySet()) {
+                    for(final Map.Entry<String, String> entry : runMode.getSettings()) {
                         pw.print("  ");
                         pw.print(entry.getKey());
                         pw.print("=");
@@ -138,7 +124,7 @@ public class ModelWriter {
                 // artifact groups
                 for(final ArtifactGroup group : runMode.getArtifactGroups()) {
                     // skip empty groups
-                    if ( group.getArtifacts().isEmpty() ) {
+                    if ( group.isEmpty() ) {
                         continue;
                     }
                     writeComment(pw, group);
@@ -147,11 +133,12 @@ public class ModelWriter {
                         pw.print(" startLevel=");
                         pw.print(String.valueOf(group.getLevel()));
                     }
+                    writeRunMode(pw, runMode);
                     pw.println("]");
                     pw.println();
 
                     // artifacts
-                    for(final Artifact ad : group.getArtifacts()) {
+                    for(final Artifact ad : group) {
                         writeComment(pw, ad);
                         pw.print("  ");
                         pw.print(ad.toMvnUrl().substring(4));
@@ -160,26 +147,27 @@ public class ModelWriter {
                             for(final Map.Entry<String, String> entry : ad.getMetadata().entrySet()) {
                                 if ( first ) {
                                     first = false;
-                                    pw.print("{ ");
+                                    pw.print(" { ");
                                 } else {
                                     pw.print(", ");
                                 }
                                 pw.print(entry.getKey());
                                 pw.print("=");
-                                pw.println(entry.getValue());
+                                pw.print(entry.getValue());
                             }
                             pw.print("}");
                         }
                         pw.println();
                     }
-                    if ( !group.getArtifacts().isEmpty() ) {
-                        pw.println();
-                    }
+                    pw.println();
                 }
 
                 // configurations
                 if ( !runMode.getConfigurations().isEmpty() ) {
-                    pw.println("[configurations]");
+                    writeComment(pw, runMode.getConfigurations());
+                    pw.print("[configurations");
+                    writeRunMode(pw, runMode);
+                    pw.println("]");
                     for(final Configuration config : runMode.getConfigurations()) {
                         writeComment(pw, config);
                         final String raw = (String)config.getProperties().get(ModelConstants.CFG_UNPROCESSED);
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
new file mode 100644
index 0000000..313f9d4
--- /dev/null
+++ b/src/test/java/org/apache/sling/provisioning/model/io/IOTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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.io;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Map;
+
+import org.apache.sling.provisioning.model.Model;
+import org.apache.sling.provisioning.model.ModelUtility;
+import org.apache.sling.provisioning.model.Traceable;
+import org.junit.Test;
+
+public class IOTest {
+
+    /**
+     * Not really a unit test...but better than nothing
+     */
+    @Test public void testReadWrite() throws Exception {
+        final Model result = new Model();
+        final String[] candidates = new String[] {"boot.txt", "example.txt", "main.txt", "oak.txt"};
+
+        for(final String name : candidates) {
+            final Reader reader = new InputStreamReader(this.getClass().getResourceAsStream("/" + name), "UTF-8");
+            try {
+                final Model current = ModelReader.read(reader, name);
+                final Map<Traceable, String> errors = ModelUtility.validate(current);
+                if (errors != null ) {
+                    throw new Exception("Invalid model at " + name + " : " + errors);
+                }
+                ModelUtility.merge(result, current);
+            } finally {
+                reader.close();
+            }
+        }
+
+        final Map<Traceable, String> errors = ModelUtility.validate(result);
+        if (errors != null ) {
+            throw new Exception("Invalid assembled model : " + errors);
+        }
+
+        // write the complete model
+        StringWriter writer = new StringWriter();
+        try {
+            ModelWriter.write(writer, result);
+        } finally {
+            writer.close();
+        }
+
+        // and read it again
+        StringReader reader = new StringReader(writer.toString());
+        final Model readModel = ModelReader.read(reader, "memory");
+        reader.close();
+        final Map<Traceable, String> readErrors = ModelUtility.validate(readModel);
+        if (readErrors != null ) {
+            throw new Exception("Invalid read model : " + readErrors);
+        }
+    }
+}
diff --git a/src/test/resources/boot.txt b/src/test/resources/boot.txt
new file mode 100644
index 0000000..7f08d39
--- /dev/null
+++ b/src/test/resources/boot.txt
@@ -0,0 +1,40 @@
+# The :launchpad feature defines Sling's launchpad version
+# Only a single artifact is allowed within this feature.
+#
+[feature name=:launchpad]
+    org.apache.sling/org.apache.sling.launchpad.base/4.4.1-2.5.2/jar
+
+
+# The :boot feature contains all things to bootstrap the installation.
+#
+[feature name=:boot]
+
+# additional entries for sling.properties
+# ---------------------------------------
+# jackrabbit and oak run modes are mutually exclusive,
+# and cannot be changed after the first startup
+[settings]
+    sling.run.mode.install.options=jackrabbit,oak
+
+[artifacts]
+    org.slf4j/slf4j-api/1.7.6/jar
+    org.apache.sling/org.apache.sling.commons.log/4.0.0/jar
+    org.apache.sling/org.apache.sling.commons.logservice/1.0.2/jar
+    org.slf4j/jcl-over-slf4j/1.7.6/jar
+    org.slf4j/log4j-over-slf4j/1.7.6/jar
+    org.apache.sling/org.apache.sling.settings/1.3.2/jar
+    org.apache.sling/org.apache.sling.fragment.xml/1.0.2/jar
+    org.apache.sling/org.apache.sling.fragment.transaction/1.0.0/jar
+    org.apache.sling/org.apache.sling.javax.activation/0.1.0/jar
+    org.apache.sling/org.apache.sling.fragment.ws/${ws.version}/jar
+    org.apache.sling/org.apache.sling.launchpad.installer/1.2.0/jar
+    org.apache.sling/org.apache.sling.installer.core/3.5.4/jar
+    org.apache.sling/org.apache.sling.installer.provider.file/1.0.4/jar
+    org.apache.felix/org.apache.felix.configadmin/1.6.0/jar
+    org.apache.felix/org.apache.felix.eventadmin/1.4.2/jar
+
+# Add an a servlet implementation for the standalone case
+[artifacts startLevel=5 runModes=:standalone]
+    org.apache.felix/org.apache.felix.http.api/2.3.0/jar
+    org.apache.felix/org.apache.felix.http.servlet-api/1.0.0/jar
+    org.apache.felix/org.apache.felix.http.jetty/2.3.0/jar
diff --git a/src/test/resources/example.txt b/src/test/resources/example.txt
new file mode 100644
index 0000000..0153cd3
--- /dev/null
+++ b/src/test/resources/example.txt
@@ -0,0 +1,64 @@
+# This is a feature description
+#
+# A feature consists of variables and run mode dependent artifacts.
+#
+[feature name=example]
+# The variables are global and can be used within artifact definitions, configurations,
+# and settings.
+#
+# Variables
+[variables]
+  ws.version=1.0.2
+
+# Settings, artifacts and configurations belong to a run mode. If none is specified
+# the default run mode is used. The same goes with the start level for artifacts
+# Framework properties
+[settings]
+    sling.options=jackrabbit,oak
+
+[artifacts]
+    commons-io/commons-io/1.4/jar
+    commons-fileupload/commons-fileupload/1.3.1/jar
+    commons-collections/commons-collections/3.2.1/jar
+    commons-codec/commons-codec/1.9/jar
+    commons-lang/commons-lang/2.6/jar
+    org.apache.commons/commons-math/2.2/jar
+# Artifacts can have additional information like a SHA1 etc.
+#
+    org.apache.commons/commons-math/2.2/jar [sha1=2353750701ABE]
+
+# A start level can be specified
+[artifacts startLevel=5]
+    org.apache.sling/org.apache.sling.extensions.webconsolebranding/1.0.0/jar
+    org.apache.sling/org.apache.sling.extensions.webconsolesecurityprovider/1.0.0/jar
+
+# And now the configurations section
+# A configuration ends with an empty line and all configurations use the Apache Felix
+# ConfigAdmin format.
+#
+[configurations]
+# A plain configuration
+org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService
+    name="Default\ NodeStore"
+    repository.home="sling/oak/repository"
+
+# A factory configuration with the alias error
+org.apache.sling.log.LoggerFactory-error
+    name="Test"
+    value="Hallo"
+
+# A configuration using properties format:
+org.apache.sling.another.config [format=properties]
+     test=A
+     value=5
+     
+ 
+
+# Now artifacts, configurations and settings can be specified. All of them belong to
+# the previous runMode definition
+#
+[artifacts startLevel=15 runModes=jackrabbit]
+    org.apache.derby/derby/10.5.3.0_1/jar
+    org.apache.sling/org.apache.sling.jcr.jackrabbit.server/2.1.3-SNAPSHOT/jar
+
+
diff --git a/src/test/resources/main.txt b/src/test/resources/main.txt
new file mode 100644
index 0000000..1afbc3f
--- /dev/null
+++ b/src/test/resources/main.txt
@@ -0,0 +1,104 @@
+[feature name=main]
+
+[variables]
+  ws.version=1.0.2
+
+[artifacts]
+    commons-io/commons-io/1.4/jar
+    commons-fileupload/commons-fileupload/1.3.1/jar
+    commons-collections/commons-collections/3.2.1/jar
+    commons-codec/commons-codec/1.9/jar
+    commons-lang/commons-lang/2.6/jar
+    org.apache.commons/commons-math/2.2/jar
+    commons-pool/commons-pool/1.6/jar
+    org.apache.servicemix.bundles/org.apache.servicemix.bundles.concurrent/1.3.4_1/jar
+    org.apache.geronimo.bundles/commons-httpclient/3.1_1/jar
+    org.apache.sling/org.apache.sling.commons.osgi/2.2.2/jar
+    org.apache.sling/org.apache.sling.commons.mime/2.1.8/jar
+    org.apache.sling/org.apache.sling.commons.classloader/1.3.2/jar
+    org.apache.sling/org.apache.sling.commons.compiler/2.2.0/jar
+    org.apache.sling/org.apache.sling.commons.scheduler/2.4.4/jar
+    org.apache.sling/org.apache.sling.commons.threads/3.2.0/jar
+    org.apache.sling/org.apache.sling.discovery.api/1.0.0/jar
+    org.apache.sling/org.apache.sling.discovery.support/1.0.0/jar
+    org.apache.sling/org.apache.sling.discovery.impl/1.0.10/jar
+    org.apache.sling/org.apache.sling.event/3.3.14/jar
+    org.apache.sling/org.apache.sling.api/2.8.0/jar
+    org.apache.sling/org.apache.sling.serviceusermapper/1.0.2/jar
+    org.apache.sling/org.apache.sling.resourceresolver/1.1.3-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.auth.core/1.2.0/jar
+    org.apache.sling/org.apache.sling.engine/2.3.5-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.auth.openid/1.0.4/jar
+    org.apache.sling/org.apache.sling.auth.form/1.0.6/jar
+    org.apache.sling/org.apache.sling.auth.selector/1.0.6/jar
+    org.apache.sling/org.apache.sling.adapter/2.1.1-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.servlets.resolver/2.3.2/jar
+    org.apache.sling/org.apache.sling.servlets.get/2.1.10/jar
+    org.apache.sling/org.apache.sling.servlets.post/2.3.6/jar
+    org.apache.sling/org.apache.sling.jcr.contentloader/2.1.8/jar
+    org.apache.sling/org.apache.sling.jcr.resource/2.3.8/jar
+    org.apache.sling/org.apache.sling.jcr.classloader/3.2.1-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.bundleresource.impl/2.2.0/jar
+    org.apache.sling/org.apache.sling.fsresource/1.1.4/jar
+    org.apache.sling/org.apache.sling.launchpad.content/2.0.8/jar
+    org.apache.sling/org.apache.sling.scripting.api/2.1.6/jar
+    org.apache.sling/org.apache.sling.scripting.core/2.0.27-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.scripting.javascript/2.0.12/jar
+    org.apache.sling/org.apache.sling.scripting.jsp/2.1.4/jar
+    org.apache.sling/org.apache.sling.scripting.jsp.taglib/2.2.2/jar
+    org.apache.geronimo.bundles/jstl/1.2_1/jar
+    org.apache.sling/org.apache.sling.models.api/1.1.0/jar
+    org.apache.sling/org.apache.sling.models.impl/1.1.0/jar
+    org.apache.felix/org.apache.felix.http.whiteboard/2.2.0/jar
+    org.apache.sling/org.apache.sling.installer.console/1.0.0/jar
+    org.apache.sling/org.apache.sling.installer.factory.configuration/1.0.14/jar
+    org.apache.sling/org.apache.sling.installer.provider.jcr/3.1.8/jar
+
+[artifacts startLevel=5]
+    org.apache.sling/org.apache.sling.extensions.webconsolebranding/1.0.0/jar
+    org.apache.sling/org.apache.sling.extensions.webconsolesecurityprovider/1.0.0/jar
+    org.apache.felix/org.apache.felix.inventory/1.0.4/jar
+    org.apache.felix/org.apache.felix.prefs/1.0.6/jar
+    org.apache.felix/org.apache.felix.webconsole/4.2.2/jar
+    org.apache.geronimo.bundles/json/20090211_1/jar
+    org.apache.felix/org.apache.felix.webconsole.plugins.ds/1.0.0/jar
+    org.apache.felix/org.apache.felix.webconsole.plugins.packageadmin/1.0.0/jar
+    org.apache.felix/org.apache.felix.webconsole.plugins.event/1.0.2/jar
+    org.apache.felix/org.apache.felix.webconsole.plugins.memoryusage/1.0.4/jar
+    org.apache.sling/org.apache.sling.commons.json/2.0.8/jar
+    org.apache.felix/org.apache.felix.bundlerepository/1.6.4/jar
+    org.apache.sling/org.apache.sling.extensions.threaddump/0.2.2/jar
+    org.apache.sling/org.apache.sling.jcr.webconsole/1.0.1-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.extensions.explorer/1.0.4/jar
+    org.apache.aries.jmx/org.apache.aries.jmx.api/1.1.0/jar
+    org.apache.aries/org.apache.aries.util/1.1.0/jar
+    org.apache.aries.jmx/org.apache.aries.jmx.core/1.1.1/jar
+    org.apache.aries.jmx/org.apache.aries.jmx.whiteboard/1.0.0/jar
+
+[artifacts startLevel=10]
+    org.apache.felix/org.apache.felix.scr/1.8.2/jar
+    org.apache.felix/org.apache.felix.metatype/1.0.10/jar
+    org.apache.tika/tika-core/1.2/jar
+    org.apache.tika/tika-bundle/1.2/jar
+
+[artifacts startLevel=15]
+    org.apache.sling/org.apache.sling.jcr.jcr-wrapper/2.0.0/jar
+    org.apache.sling/org.apache.sling.jcr.api/2.2.0/jar
+    org.apache.sling/org.apache.sling.jcr.base/2.2.2/jar
+    org.apache.sling/org.apache.sling.jcr.registration/1.0.1-SNAPSHOT/jar
+    org.apache.jackrabbit/jackrabbit-api/2.7.5/jar
+    org.apache.jackrabbit/jackrabbit-jcr-commons/2.7.5/jar
+    org.apache.jackrabbit/jackrabbit-spi/2.7.1/jar
+    org.apache.jackrabbit/jackrabbit-spi-commons/2.7.1/jar
+    org.apache.jackrabbit/jackrabbit-webdav/2.7.1/jar
+    org.apache.jackrabbit/jackrabbit-jcr-rmi/2.7.1/jar
+    org.apache.sling/org.apache.sling.jcr.webdav/2.2.2/jar
+    org.apache.sling/org.apache.sling.jcr.davex/1.2.1-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.jcr.jackrabbit.usermanager/2.2.1-SNAPSHOT/jar
+    org.apache.sling/org.apache.sling.jcr.jackrabbit.accessmanager/2.1.1-SNAPSHOT/jar
+
+[artifacts startLevel=15 runModes=jackrabbit]
+    org.apache.derby/derby/10.5.3.0_1/jar
+    org.apache.sling/org.apache.sling.jcr.jackrabbit.server/2.1.3-SNAPSHOT/jar
+
+
diff --git a/src/test/resources/oak.txt b/src/test/resources/oak.txt
new file mode 100644
index 0000000..936c2ac
--- /dev/null
+++ b/src/test/resources/oak.txt
@@ -0,0 +1,19 @@
+# This is the OAK feature.
+[feature name=oak]
+# All bundles are defined to be started at start level 15
+# The segment node store is used via a configuration
+[artifacts startLevel=15 runModes=oak]
+    org.apache.sling/org.apache.sling.jcr.oak.server/0.0.2-SNAPSHOT/jar
+    com.google.guava/guava/15.0/jar
+    org.apache.jackrabbit/oak-core/1.0.0/jar
+    org.apache.jackrabbit/oak-commons/1.0.0/jar
+    org.apache.jackrabbit/oak-mk/1.0.0/jar
+    org.apache.jackrabbit/oak-mk-api/1.0.0/jar
+    org.apache.jackrabbit/oak-mk-remote/1.0.0/jar
+    org.apache.jackrabbit/oak-lucene/1.0.0/jar
+    org.apache.jackrabbit/oak-blob/1.0.0/jar
+
+[configurations runModes=oak]
+  org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService
+    name="Default\ NodeStore"
+    repository.home="sling/oak/repository"

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.