SLING-7827 : Remove Application concept from feature model
diff --git a/src/main/java/org/apache/sling/feature/io/IOUtils.java b/src/main/java/org/apache/sling/feature/io/IOUtils.java
index 70af38c..53b65c4 100644
--- a/src/main/java/org/apache/sling/feature/io/IOUtils.java
+++ b/src/main/java/org/apache/sling/feature/io/IOUtils.java
@@ -16,11 +16,6 @@
  */
 package org.apache.sling.feature.io;
 
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.io.json.FeatureJSONReader;
-import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
-
 import java.io.File;
 import java.io.FileReader;
 import java.io.IOException;
@@ -30,6 +25,10 @@
 import java.util.Comparator;
 import java.util.List;
 
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.json.FeatureJSONReader;
+
 public class IOUtils {
 
     /** The extension for a reference file. */
@@ -166,18 +165,16 @@
      *
      * @param url The feature url
      * @param artifactManager The artifact manager to read the feature
-     * @param substituteVariables Variable substitution handling
      * @return The read feature
      * @throws IOException If reading fails
      */
     public static Feature getFeature(final String url,
-            final ArtifactManager artifactManager,
-            final SubstituteVariables substituteVariables)
+            final ArtifactManager artifactManager)
     throws IOException {
         final ArtifactHandler featureArtifact = artifactManager.getArtifactHandler(url);
 
         try (final FileReader r = new FileReader(featureArtifact.getFile())) {
-            final Feature f = FeatureJSONReader.read(r, featureArtifact.getUrl(), substituteVariables);
+            final Feature f = FeatureJSONReader.read(r, featureArtifact.getUrl());
             return f;
         }
     }
@@ -191,7 +188,7 @@
     public static ArtifactId getFelixFrameworkId(final String version) {
         return new ArtifactId("org.apache.felix",
                 "org.apache.felix.framework",
-                version != null ? version : "5.6.10", null, null);
+                version != null ? version : "6.0.0", null, null);
     }
 
     static final Comparator<String> FEATURE_PATH_COMP = new Comparator<String>() {
diff --git a/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONReader.java b/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONReader.java
deleted file mode 100644
index 05382ca..0000000
--- a/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONReader.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * 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.feature.io.json;
-
-import org.apache.felix.configurator.impl.json.JSONUtil;
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.KeyValueMap;
-
-import java.io.IOException;
-import java.io.Reader;
-import java.io.StringReader;
-import java.util.Collections;
-import java.util.Map;
-
-import javax.json.Json;
-import javax.json.JsonObject;
-
-/**
- * This class offers a method to read an {@code Application} using a {@code Reader} instance.
- */
-public class ApplicationJSONReader extends JSONReaderBase {
-
-    /**
-     * Read a new application from the reader
-     * The reader is not closed. It is up to the caller to close the reader.
-     *
-     * @param reader The reader for the feature
-     * @return The application
-     * @throws IOException If an IO errors occurs or the JSON is invalid.
-     */
-    public static Application read(final Reader reader) throws IOException {
-        return read(reader, Collections.emptyMap());
-    }
-
-    /**
-     * Read a new application from the reader
-     * The reader is not closed. It is up to the caller to close the reader.
-     *
-     * @param reader The reader for the feature
-     * @param overriddenVariables Map of variables that override the variable
-     * values as in the application JSON
-     * @return The application
-     * @throws IOException If an IO errors occurs or the JSON is invalid.
-     */
-    public static Application read(final Reader reader, Map<String, String> overriddenVariables)
-    throws IOException {
-        try {
-            final ApplicationJSONReader mr = new ApplicationJSONReader();
-
-            mr.readApplication(reader, overriddenVariables);
-            return mr.app;
-        } catch (final IllegalStateException | IllegalArgumentException e) {
-            throw new IOException(e);
-        }
-    }
-
-    /** The read application. */
-    private final Application app;
-
-    /** This holds the application variables including any overrides provided,
-     * e.g. via the launcher commandline.
-     */
-    private volatile KeyValueMap variables;
-
-    /**
-     * Private constructor
-     */
-    private ApplicationJSONReader() {
-        super(null);
-        this.app = new Application();
-    }
-
-    /**
-     * Read a full application
-     * @param reader The reader
-     * @param overriddenVariables
-     * @throws IOException If an IO error occurs or the JSON is not valid.
-     */
-    private void readApplication(final Reader reader, Map<String, String> overriddenVariables)
-    throws IOException {
-        final JsonObject json = Json.createReader(new StringReader(minify(reader))).readObject();
-
-        @SuppressWarnings("unchecked")
-        final Map<String, Object> map = (Map<String, Object>) JSONUtil.getValue(json);
-
-        final String frameworkId = this.getProperty(map, JSONConstants.APP_FRAMEWORK);
-        if ( frameworkId != null ) {
-            app.setFramework(ArtifactId.parse(frameworkId));
-        }
-        this.readVariables(map, app.getVariables());
-
-        this.variables = new KeyValueMap();
-        this.variables.putAll(app.getVariables());
-
-        // Apply the overrides
-        for (Map.Entry<String, String> entry : overriddenVariables.entrySet()) {
-            variables.put(entry.getKey(), entry.getValue());
-        }
-
-        this.readBundles(map, app.getBundles(), app.getConfigurations());
-        this.readFrameworkProperties(map, app.getFrameworkProperties());
-        this.readConfigurations(map, app.getConfigurations());
-
-        this.readExtensions(map,
-                JSONConstants.APP_KNOWN_PROPERTIES,
-                this.app.getExtensions(), this.app.getConfigurations());
-    }
-
-    @Override
-    protected Object handleResolveVars(Object val) {
-        return handleVars(val, variables);
-    }
-
-    @Override
-    protected Object handleLaunchVars(Object val) {
-        return handleVars(val, variables);
-    }
-}
-
-
diff --git a/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONWriter.java b/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONWriter.java
deleted file mode 100644
index 89a8366..0000000
--- a/src/main/java/org/apache/sling/feature/io/json/ApplicationJSONWriter.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * 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.feature.io.json;
-
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Collections;
-
-import javax.json.Json;
-import javax.json.JsonArrayBuilder;
-import javax.json.JsonObjectBuilder;
-import javax.json.JsonWriter;
-import javax.json.JsonWriterFactory;
-import javax.json.stream.JsonGenerator;
-
-/**
- * Simple JSON writer for an application
- */
-public class ApplicationJSONWriter extends JSONWriterBase {
-
-    /**
-     * Writes the application to the writer.
-     * The writer is not closed.
-     * @param writer Writer
-     * @param app The application
-     * @throws IOException If writing fails
-     */
-    public static void write(final Writer writer, final Application app)
-    throws IOException {
-        final ApplicationJSONWriter w = new ApplicationJSONWriter();
-        w.writeApp(writer, app);
-    }
-
-   private void writeApp(final Writer writer, final Application app)
-    throws IOException {
-       JsonObjectBuilder ob = Json.createObjectBuilder();
-
-        // framework
-        if ( app.getFramework() != null ) {
-            ob.add(JSONConstants.APP_FRAMEWORK, app.getFramework().toMvnId());
-        }
-
-        // variables
-        writeVariables(ob, app.getVariables());
-
-        // features
-        if ( !app.getFeatureIds().isEmpty() ) {
-            JsonArrayBuilder featuresArr = Json.createArrayBuilder();
-
-            for(final ArtifactId id : app.getFeatureIds()) {
-                featuresArr.add(id.toMvnId());
-            }
-            ob.add(JSONConstants.APP_FEATURES, featuresArr.build());
-        }
-
-        // bundles
-        writeBundles(ob, app.getBundles(), app.getConfigurations());
-
-        // configurations
-        final Configurations cfgs = new Configurations();
-        for(final Configuration cfg : app.getConfigurations()) {
-            final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
-            if (  artifactProp == null ) {
-                cfgs.add(cfg);
-            }
-        }
-        writeConfigurations(ob, cfgs);
-
-        // framework properties
-        writeFrameworkProperties(ob, app.getFrameworkProperties());
-
-        // extensions
-        writeExtensions(ob, app.getExtensions(), app.getConfigurations());
-
-        JsonWriterFactory writerFactory = Json.createWriterFactory(
-                Collections.singletonMap(JsonGenerator.PRETTY_PRINTING, true));
-        JsonWriter jw = writerFactory.createWriter(writer);
-        jw.writeObject(ob.build());
-        jw.close();
-    }
-}
diff --git a/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONReader.java b/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONReader.java
index 5fca482..7a9e05a 100644
--- a/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONReader.java
+++ b/src/main/java/org/apache/sling/feature/io/json/ConfigurationJSONReader.java
@@ -16,17 +16,18 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.felix.configurator.impl.json.JSONUtil;
-import org.apache.sling.feature.Configurations;
-
-import javax.json.Json;
-import javax.json.JsonObject;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
 import java.util.Collections;
 import java.util.Map;
 
+import javax.json.Json;
+import javax.json.JsonObject;
+
+import org.apache.felix.configurator.impl.json.JSONUtil;
+import org.apache.sling.feature.Configurations;
+
 /**
  * JSON Reader for configurations.
  */
@@ -51,6 +52,11 @@
         }
     }
 
+    @Override
+    protected Object handleResolveVars(final Object val) {
+        return val;
+    }
+
     /**
      * Private constructor
      * @param location Optional location
diff --git a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONReader.java b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONReader.java
index 0862253..04fc850 100644
--- a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONReader.java
+++ b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONReader.java
@@ -33,6 +33,7 @@
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.Include;
+import org.apache.sling.feature.builder.FeatureBuilder;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
@@ -40,7 +41,6 @@
  * This class offers a method to read a {@code Feature} using a {@code Reader} instance.
  */
 public class FeatureJSONReader extends JSONReaderBase {
-    public enum SubstituteVariables { NONE, RESOLVE, LAUNCH }
 
     /**
      * Read a new feature from the reader
@@ -48,13 +48,12 @@
      *
      * @param reader The reader for the feature
      * @param location Optional location
-     * @param phase If variables need to be substituted, the phase the this should be done for.
      * @return The read feature
      * @throws IOException If an IO errors occurs or the JSON is invalid.
      */
-    public static Feature read(final Reader reader, final String location, final SubstituteVariables phase)
+    public static Feature read(final Reader reader, final String location)
     throws IOException {
-        return read(reader, null, location, phase);
+        return read(reader, null, location);
     }
 
     /**
@@ -64,17 +63,15 @@
      * @param reader The reader for the feature
      * @param providedId Optional artifact id
      * @param location Optional location
-     * @param phase If variables need to be substituted, the phase the this should be done for.
      * @return The read feature
      * @throws IOException If an IO errors occurs or the JSON is invalid.
      */
     public static Feature read(final Reader reader,
             final ArtifactId providedId,
-            final String location,
-            final SubstituteVariables phase)
+            final String location)
     throws IOException {
         try {
-            final FeatureJSONReader mr = new FeatureJSONReader(providedId, location, phase);
+            final FeatureJSONReader mr = new FeatureJSONReader(providedId, location);
             return mr.readFeature(reader);
         } catch (final IllegalStateException | IllegalArgumentException e) {
             throw new IOException(e);
@@ -87,18 +84,14 @@
     /** The provided id. */
     private final ArtifactId providedId;
 
-    /** The current reading phase. */
-    private final SubstituteVariables phase;
-
     /**
      * Private constructor
      * @param pId Optional id
      * @param location Optional location
      */
-    FeatureJSONReader(final ArtifactId pId, final String location, final SubstituteVariables phase) {
+    FeatureJSONReader(final ArtifactId pId, final String location) {
         super(location);
         this.providedId = pId;
-        this.phase = phase;
     }
 
     /**
@@ -161,20 +154,8 @@
     }
 
     @Override
-    protected Object handleResolveVars(Object val) {
-        if (phase == SubstituteVariables.RESOLVE) {
-            return handleVars(val, feature.getVariables());
-        } else {
-            return val;
-        }
-    }
-
-    @Override
-    protected Object handleLaunchVars(Object val) {
-        if (phase == SubstituteVariables.LAUNCH) {
-            return handleVars(val, feature.getVariables());
-        }
-        return val;
+    protected Object handleResolveVars(final Object val) {
+        return FeatureBuilder.replaceVariables(val, null, this.feature);
     }
 
     private void readIncludes(final Map<String, Object> map) throws IOException {
diff --git a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
index 57428e1..6d1776b 100644
--- a/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
+++ b/src/main/java/org/apache/sling/feature/io/json/FeatureJSONWriter.java
@@ -16,14 +16,6 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.sling.feature.ArtifactId;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.Include;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
-
 import java.io.IOException;
 import java.io.Writer;
 import java.util.Collections;
@@ -37,6 +29,14 @@
 import javax.json.JsonWriterFactory;
 import javax.json.stream.JsonGenerator;
 
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.Include;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+
 /**
  * Simple JSON writer for a feature
  */
@@ -185,7 +185,7 @@
         // configurations
         final Configurations cfgs = new Configurations();
         for(final Configuration cfg : feature.getConfigurations()) {
-            final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
+            final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT_ID);
             if (  artifactProp == null ) {
                 cfgs.add(cfg);
             }
diff --git a/src/main/java/org/apache/sling/feature/io/json/JSONConstants.java b/src/main/java/org/apache/sling/feature/io/json/JSONConstants.java
index 5fd9a5a..6aeef1a 100644
--- a/src/main/java/org/apache/sling/feature/io/json/JSONConstants.java
+++ b/src/main/java/org/apache/sling/feature/io/json/JSONConstants.java
@@ -16,40 +16,40 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.sling.feature.Configuration;
-
 import java.util.Arrays;
 import java.util.List;
 
+import org.apache.sling.feature.Configuration;
+
 public abstract class JSONConstants {
 
-    public static final String FEATURE_ID = "id";
+    static final String FEATURE_ID = "id";
 
-    public static final String FEATURE_VARIABLES = "variables";
+    static final String FEATURE_VARIABLES = "variables";
 
-    public static final String FEATURE_BUNDLES = "bundles";
+    static final String FEATURE_BUNDLES = "bundles";
 
-    public static final String FEATURE_FRAMEWORK_PROPERTIES = "framework-properties";
+    static final String FEATURE_FRAMEWORK_PROPERTIES = "framework-properties";
 
-    public static final String FEATURE_CONFIGURATIONS = "configurations";
+    static final String FEATURE_CONFIGURATIONS = "configurations";
 
-    public static final String FEATURE_INCLUDES = "includes";
+    static final String FEATURE_INCLUDES = "includes";
 
-    public static final String FEATURE_REQUIREMENTS = "requirements";
+    static final String FEATURE_REQUIREMENTS = "requirements";
 
-    public static final String FEATURE_CAPABILITIES = "capabilities";
+    static final String FEATURE_CAPABILITIES = "capabilities";
 
-    public static final String FEATURE_TITLE = "title";
+    static final String FEATURE_TITLE = "title";
 
-    public static final String FEATURE_DESCRIPTION = "description";
+    static final String FEATURE_DESCRIPTION = "description";
 
-    public static final String FEATURE_VENDOR = "vendor";
+    static final String FEATURE_VENDOR = "vendor";
 
-    public static final String FEATURE_LICENSE = "license";
+    static final String FEATURE_LICENSE = "license";
 
-    public static final String FEATURE_MODEL_VERSION = "model-version";
+    static final String FEATURE_MODEL_VERSION = "model-version";
 
-    public static final List<String> FEATURE_KNOWN_PROPERTIES = Arrays.asList(FEATURE_ID,
+    static final List<String> FEATURE_KNOWN_PROPERTIES = Arrays.asList(FEATURE_ID,
             FEATURE_MODEL_VERSION,
             FEATURE_VARIABLES,
             FEATURE_BUNDLES,
@@ -63,27 +63,17 @@
             FEATURE_VENDOR,
             FEATURE_LICENSE);
 
-    public static final String ARTIFACT_ID = "id";
+    static final String ARTIFACT_ID = "id";
 
-    public static final List<String> ARTIFACT_KNOWN_PROPERTIES = Arrays.asList(ARTIFACT_ID,
-            Configuration.PROP_ARTIFACT,
+    static final List<String> ARTIFACT_KNOWN_PROPERTIES = Arrays.asList(ARTIFACT_ID,
+            Configuration.PROP_ARTIFACT_ID,
             FEATURE_CONFIGURATIONS);
 
-    public static final String INCLUDE_REMOVALS = "removals";
+    static final String INCLUDE_REMOVALS = "removals";
 
-    public static final String INCLUDE_EXTENSION_REMOVALS = "extensions";
+    static final String INCLUDE_EXTENSION_REMOVALS = "extensions";
 
-    public static final String REQCAP_NAMESPACE = "namespace";
-    public static final String REQCAP_ATTRIBUTES = "attributes";
-    public static final String REQCAP_DIRECTIVES = "directives";
-
-    public static final String APP_FRAMEWORK = "frameworkId";
-    public static final String APP_FEATURES = "features";
-
-    public static final List<String> APP_KNOWN_PROPERTIES = Arrays.asList(APP_FRAMEWORK,
-            FEATURE_VARIABLES,
-            FEATURE_BUNDLES,
-            FEATURE_FRAMEWORK_PROPERTIES,
-            FEATURE_CONFIGURATIONS,
-            APP_FEATURES);
+    static final String REQCAP_NAMESPACE = "namespace";
+    static final String REQCAP_ATTRIBUTES = "attributes";
+    static final String REQCAP_DIRECTIVES = "directives";
 }
diff --git a/src/main/java/org/apache/sling/feature/io/json/JSONReaderBase.java b/src/main/java/org/apache/sling/feature/io/json/JSONReaderBase.java
index 2ee18ef..1e57427 100644
--- a/src/main/java/org/apache/sling/feature/io/json/JSONReaderBase.java
+++ b/src/main/java/org/apache/sling/feature/io/json/JSONReaderBase.java
@@ -31,8 +31,6 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import javax.json.Json;
 import javax.json.JsonArrayBuilder;
@@ -59,8 +57,6 @@
  * Common methods for JSON reading.
  */
 abstract class JSONReaderBase {
-    // The pattern that variables in Feature/Application JSON follow
-    private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\$\\{[a-zA-Z0-9.-_]+\\}");
 
     /** The optional location. */
     protected final String location;
@@ -92,7 +88,8 @@
         return contents;
     }
 
-    /** Get the JSON object as a map, removing all comments that start with a '#' character
+    /**
+     * Get the JSON object as a map, removing all comments that start with a '#' character
      * @param json The JSON object to process
      * @return A map representing the JSON object.
      */
@@ -163,7 +160,7 @@
                 if (kvMap.get(key) != null) {
                     throw new IOException(this.exceptionPrefix + "Duplicate variable " + key);
                 }
-                String value = "" + entry.getValue();
+                String value = entry.getValue().toString();
                 kvMap.put(key, value);
                 variables.put(key, value);
             }
@@ -193,12 +190,7 @@
             for(final Artifact a : list) {
                 Artifact sameFound = container.getSame(a.getId());
                 if ( sameFound != null) {
-                    String str1 = a.getMetadata().get("run-modes");
-                    String str2 = sameFound.getMetadata().get("run-modes");
-
-                    if (str1 == null ? str2 == null : str1.equals(str2)) {
-                        throw new IOException(exceptionPrefix + "Duplicate bundle " + a.getId().toMvnId());
-                    }
+                    throw new IOException(exceptionPrefix + "Duplicate bundle " + a.getId().toMvnId());
                 }
                 try {
                     // check start order
@@ -249,8 +241,7 @@
                 }
                 if ( bundleObj.containsKey(JSONConstants.FEATURE_CONFIGURATIONS) ) {
                     checkType(artifactType + " configurations", bundleObj.get(JSONConstants.FEATURE_CONFIGURATIONS), Map.class);
-                    List<Configuration> bundleConfigs = addConfigurations(bundleObj, artifact, container);
-                    artifact.getMetadata().put(JSONConstants.FEATURE_CONFIGURATIONS, bundleConfigs);
+                    addConfigurations(bundleObj, artifact, container);
                 }
             }
             artifacts.add(artifact);
@@ -261,24 +252,11 @@
      * These are variables in features, artifacts (such as bundles), requirements
      * and capabilities.
      * @param val The value that may contain a variable.
-     * @return The value with the variable substitiuted.
+     * @return The value with the variable substituted.
      */
-    protected Object handleResolveVars(Object val) {
-        // No variable substitution at this level, but subclasses can add this in
-        return val;
-    }
+    abstract protected Object handleResolveVars(Object val);
 
-    /** Substitutes variables that need to be substituted at launch time.
-     * These are all variables that are not needed by the resolver.
-     * @param val The value that may contain a variable.
-     * @return The value with the variable substitiuted.
-     */
-    protected Object handleLaunchVars(Object val) {
-        // No variable substitution at this level, but subclasses can add this in
-        return val;
-    }
-
-    protected List<Configuration> addConfigurations(final Map<String, Object> map,
+    protected void addConfigurations(final Map<String, Object> map,
             final Artifact artifact,
             final Configurations container) throws IOException {
         final JSONUtil.Report report = new JSONUtil.Report();
@@ -299,7 +277,6 @@
             throw new IOException(builder.toString());
         }
 
-        List<Configuration> newConfigs = new ArrayList<>();
         for(final Config c : configs) {
             final int pos = c.getPid().indexOf('~');
             final Configuration config;
@@ -315,13 +292,13 @@
                     throw new IOException(exceptionPrefix + "Configuration must not define configurator property " + key);
                 }
                 final Object val = c.getProperties().get(key);
-                config.getProperties().put(key, handleLaunchVars(val));
+                config.getProperties().put(key, val);
             }
-            if ( config.getProperties().get(Configuration.PROP_ARTIFACT) != null ) {
-                throw new IOException(exceptionPrefix + "Configuration must not define property " + Configuration.PROP_ARTIFACT);
+            if ( config.getProperties().get(Configuration.PROP_ARTIFACT_ID) != null ) {
+                throw new IOException(exceptionPrefix + "Configuration must not define property " + Configuration.PROP_ARTIFACT_ID);
             }
             if ( artifact != null ) {
-                config.getProperties().put(Configuration.PROP_ARTIFACT, artifact.getId().toMvnId());
+                config.getProperties().put(Configuration.PROP_ARTIFACT_ID, artifact.getId().toMvnId());
             }
             for(final Configuration current : container) {
                 if ( current.equals(config) ) {
@@ -329,9 +306,7 @@
                 }
             }
             container.add(config);
-            newConfigs.add(config);
         }
-        return newConfigs;
     }
 
 
@@ -356,7 +331,7 @@
                 if ( container.get(entry.getKey()) != null ) {
                     throw new IOException(this.exceptionPrefix + "Duplicate framework property " + entry.getKey());
                 }
-                container.put(entry.getKey(), handleLaunchVars(entry.getValue()).toString());
+                container.put(entry.getKey(), entry.getValue().toString());
             }
 
         }
@@ -395,9 +370,6 @@
                     optional = postfix.substring(sep + 1);
                 }
             }
-            if ( JSONConstants.APP_KNOWN_PROPERTIES.contains(name) ) {
-                throw new IOException(this.exceptionPrefix + "Extension is using reserved name : " + name);
-            }
             if ( JSONConstants.FEATURE_KNOWN_PROPERTIES.contains(name) ) {
                 throw new IOException(this.exceptionPrefix + "Extension is using reserved name : " + name);
             }
@@ -517,42 +489,4 @@
             throw new IOException(this.exceptionPrefix + "Key " + key + " is not one of the allowed types " + Arrays.toString(types) + " : " + val.getClass());
         }
     }
-
-    /**
-     * Substitute variables in the provided value. The variables must follow the
-     * syntax ${variable_name} and are looked up in the provided variables.
-     * If the provided value contains no variables, it will be returned as-is.
-     * @param value The value that can contain variables
-     * @param variables The variables that can be substituted.
-     * @return The value with the variables substituted.
-     * @throws IllegalStateException when a variable in the value is not present
-     * in the variables map.
-     */
-    protected Object handleVars(Object value, KeyValueMap variables) {
-        if (!(value instanceof String)) {
-            return value;
-        }
-
-        String textWithVars = (String) value;
-
-        Matcher m = VARIABLE_PATTERN.matcher(textWithVars.toString());
-        StringBuffer sb = new StringBuffer();
-        while (m.find()) {
-            String var = m.group();
-
-            int len = var.length();
-            String name = var.substring(2, len - 1);
-            String val = variables.get(name);
-            if (val != null) {
-                m.appendReplacement(sb, Matcher.quoteReplacement(val));
-            } else {
-                throw new IllegalStateException("Undefined variable: " + name);
-            }
-        }
-        m.appendTail(sb);
-
-        return sb.toString();
-    }
 }
-
-
diff --git a/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java b/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
index 388bc69..9b7378b 100644
--- a/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
+++ b/src/main/java/org/apache/sling/feature/io/json/JSONWriterBase.java
@@ -16,14 +16,6 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.sling.feature.Artifact;
-import org.apache.sling.feature.Bundles;
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Configurations;
-import org.apache.sling.feature.Extension;
-import org.apache.sling.feature.ExtensionType;
-import org.apache.sling.feature.KeyValueMap;
-
 import java.io.StringReader;
 import java.lang.reflect.Array;
 import java.util.Enumeration;
@@ -36,10 +28,19 @@
 import javax.json.JsonObjectBuilder;
 import javax.json.JsonStructure;
 
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Bundles;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Configurations;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.KeyValueMap;
+
 /**
  * Common functionality for writing JSON
  */
 abstract class JSONWriterBase {
+
     protected void writeBundles(final JsonObjectBuilder ob,
             final Bundles bundles,
             final Configurations allConfigs) {
@@ -50,16 +51,9 @@
             for(final Artifact artifact : bundles) {
                 final Configurations cfgs = new Configurations();
                 for(final Configuration cfg : allConfigs) {
-                    String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
-                    if (artifactProp != null) {
-                        if (artifactProp.startsWith("mvn:")) {
-                            // Change Maven URL to maven GAV syntax
-                            artifactProp = artifactProp.substring("mvn:".length());
-                            artifactProp = artifactProp.replace('/', ':');
-                        }
-                        if (artifact.getId().toMvnId().equals(artifactProp)) {
-                            cfgs.add(cfg);
-                        }
+                    final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT_ID);
+                    if ( artifact.getId().toMvnId().equals(artifactProp) ) {
+                        cfgs.add(cfg);
                     }
                 }
                 KeyValueMap md = artifact.getMetadata();
@@ -69,18 +63,6 @@
                     JsonObjectBuilder bundleObj = Json.createObjectBuilder();
                     bundleObj.add(JSONConstants.ARTIFACT_ID, artifact.getId().toMvnId());
 
-                    if (md.get("start-level") == null) {
-                        String so = md.get("start-order");
-                        if (so != null) {
-                            md.put("start-level", so);
-                        }
-                    }
-
-                    Object runmodes = md.remove("runmodes");
-                    if (runmodes instanceof String) {
-                        md.put("run-modes", runmodes);
-                    }
-
                     for(final Map.Entry<String, String> me : md) {
                         bundleObj.add(me.getKey(), me.getValue());
                     }
@@ -125,7 +107,7 @@
             final Enumeration<String> e = cfg.getProperties().keys();
             while ( e.hasMoreElements() ) {
                 final String name = e.nextElement();
-                if ( Configuration.PROP_ARTIFACT.equals(name) ) {
+                if ( Configuration.PROP_ARTIFACT_ID.equals(name) ) {
                     continue;
                 }
 
@@ -232,7 +214,7 @@
                 for(final Artifact artifact : ext.getArtifacts()) {
                     final Configurations artifactCfgs = new Configurations();
                     for(final Configuration cfg : allConfigs) {
-                        final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT);
+                        final String artifactProp = (String)cfg.getProperties().get(Configuration.PROP_ARTIFACT_ID);
                         if (  artifact.getId().toMvnId().equals(artifactProp) ) {
                             artifactCfgs.add(cfg);
                         }
diff --git a/src/test/java/org/apache/sling/feature/io/json/ApplicationJSONReaderTest.java b/src/test/java/org/apache/sling/feature/io/json/ApplicationJSONReaderTest.java
deleted file mode 100644
index 55c9145..0000000
--- a/src/test/java/org/apache/sling/feature/io/json/ApplicationJSONReaderTest.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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.feature.io.json;
-
-import org.apache.sling.feature.Application;
-import org.apache.sling.feature.Configuration;
-import org.junit.Test;
-
-import java.io.File;
-import java.io.FileReader;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.junit.Assert.assertEquals;
-
-public class ApplicationJSONReaderTest {
-    @Test
-    public void testOverrideVariables() throws Exception {
-        File appFile = new File(getClass().getResource("/applications/app_with_vars.json").toURI());
-        FileReader fr = new FileReader(appFile);
-
-        Map<String, String> overridden = new HashMap<>();
-        overridden.put("web.port", "12345");
-        overridden.put("something", "else");
-
-        Application app = ApplicationJSONReader.read(fr, overridden);
-        Configuration cfg = app.getConfigurations().getConfiguration("org.apache.felix.http");
-        assertEquals("12345", cfg.getProperties().get("org.osgi.service.http.port"));
-    }
-
-    @Test
-    public void testDefaultVariables() throws Exception {
-        File appFile = new File(getClass().getResource("/applications/app_with_vars.json").toURI());
-        FileReader fr = new FileReader(appFile);
-
-        Application app = ApplicationJSONReader.read(fr);
-        Configuration cfg = app.getConfigurations().getConfiguration("org.apache.felix.http");
-        assertEquals("8888", cfg.getProperties().get("org.osgi.service.http.port"));
-    }
-}
diff --git a/src/test/java/org/apache/sling/feature/io/json/FeatureJSONReaderTest.java b/src/test/java/org/apache/sling/feature/io/json/FeatureJSONReaderTest.java
index 6ee87e3..b3a3ee0 100644
--- a/src/test/java/org/apache/sling/feature/io/json/FeatureJSONReaderTest.java
+++ b/src/test/java/org/apache/sling/feature/io/json/FeatureJSONReaderTest.java
@@ -16,6 +16,16 @@
  */
 package org.apache.sling.feature.io.json;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.List;
+
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.Bundles;
 import org.apache.sling.feature.Configuration;
@@ -25,25 +35,10 @@
 import org.apache.sling.feature.Feature;
 import org.apache.sling.feature.Include;
 import org.apache.sling.feature.KeyValueMap;
-import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
 import org.junit.Test;
 import org.osgi.resource.Capability;
 import org.osgi.resource.Requirement;
 
-import java.lang.reflect.Field;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Dictionary;
-import java.util.Enumeration;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
 public class FeatureJSONReaderTest {
 
     @Test public void testRead() throws Exception {
@@ -119,63 +114,6 @@
         assertEquals("c${c_config}c", props.get("c.value"));
     }
 
-    @Test public void testReadWithVariablesLaunch() throws Exception {
-        final Feature feature = U.readFeature("test3", SubstituteVariables.LAUNCH);
-
-        List<Include> includes = feature.getIncludes();
-        assertEquals(1, includes.size());
-        Include include = includes.get(0);
-        assertEquals("Include substitution should not happen at launch time",
-                "${sling.gid}:sling:10", include.getId().toMvnId());
-
-        List<Requirement> reqs = feature.getRequirements();
-        Requirement req = reqs.get(0);
-        assertEquals("Requirement substitution should not happen at launch time",
-                "osgi.${ns}", req.getNamespace());
-        assertEquals("Requirement substitution should not happen at launch time",
-                "(&(osgi.contract=${contract.name})(&(version>=3.0)(!(version>=4.0))))",
-                req.getDirectives().get("filter"));
-        assertEquals("There should be 1 directive, comments should be ignored",
-                1, req.getDirectives().size());
-
-        List<Capability> caps = feature.getCapabilities();
-        Capability cap = null;
-        for (Capability c : caps) {
-            if ("osgi.${svc}".equals(c.getNamespace())) {
-                cap = c;
-                break;
-            }
-        }
-        assertEquals("Capability substitution should not happen at launch time",
-                Collections.singletonList("org.osgi.${svc}.http.runtime.HttpServiceRuntime"),
-                cap.getAttributes().get("objectClass"));
-        assertEquals("There should be 1 attribute, comments should be ignored",
-                1, cap.getAttributes().size());
-
-        KeyValueMap fwProps = feature.getFrameworkProperties();
-        assertEquals("something", fwProps.get("brave"));
-        assertEquals("There should be 3 framework properties, comments not included",
-                3, fwProps.size());
-
-        Configurations configurations = feature.getConfigurations();
-        assertEquals("There should be 2 configurations, comments not included",
-                2, configurations.size());
-
-        Configuration config1 = configurations.getConfiguration("my.pid2");
-        for (Enumeration<?> en = config1.getProperties().elements(); en.hasMoreElements(); ) {
-            assertFalse("The comment should not show up in the configuration",
-                "comment".equals(en.nextElement()));
-        }
-
-        Configuration config2 = configurations.getConfiguration("my.pid2");
-        Dictionary<String, Object> props = config2.getProperties();
-        assertEquals("aaright!", props.get("a.value"));
-        assertEquals("right!bb", props.get("b.value"));
-        assertEquals("creally?c", props.get("c.value"));
-        assertEquals("The variable definition looks like a variable definition, this escaping mechanism should work",
-                "${refvar}", props.get("refvar"));
-    }
-
     @Test public void testReadRepoInitExtension() throws Exception {
         Feature feature = U.readFeature("repoinit");
         Extensions extensions = feature.getExtensions();
@@ -192,36 +130,8 @@
         assertEquals("[\"some repo init\",\"text\"]", ext.getJSON());
     }
 
-    @Test public void testHandleVars() throws Exception {
-        FeatureJSONReader reader = new FeatureJSONReader(null, null, SubstituteVariables.LAUNCH);
-
-        ArtifactId aid = new ArtifactId("gid", "aid", "1.2.3", null, null);
-        Feature feature = new Feature(aid);
-        KeyValueMap kvMap = feature.getVariables();
-        kvMap.put("var1", "bar");
-        kvMap.put("varvariable", "${myvar}");
-        kvMap.put("var.2", "2");
-        setPrivateField(reader, "feature", feature);
-
-        assertEquals("foobarfoo", reader.handleLaunchVars("foo${var1}foo"));
-        assertEquals("barbarbar", reader.handleLaunchVars("${var1}${var1}${var1}"));
-        assertEquals("${}test${myvar}2", reader.handleLaunchVars("${}test${varvariable}${var.2}"));
-        try {
-            reader.handleLaunchVars("${undefined}");
-            fail("Should throw an exception on the undefined variable");
-        } catch (IllegalStateException ise) {
-            // good
-        }
-    }
-
     @Test public void testReadArtifactsExtensions() throws Exception {
-        final Feature feature = U.readFeature("artifacts-extension", SubstituteVariables.NONE);
+        final Feature feature = U.readFeature("artifacts-extension");
         ArtifactsExtensions.testReadArtifactsExtensions(feature);
     }
-
-    private void setPrivateField(Object obj, String name, Object value) throws Exception {
-        Field field = obj.getClass().getDeclaredField(name);
-        field.setAccessible(true);
-        field.set(obj, value);
-    }
 }
diff --git a/src/test/java/org/apache/sling/feature/io/json/FeatureJSONWriterTest.java b/src/test/java/org/apache/sling/feature/io/json/FeatureJSONWriterTest.java
index 79609f2..1e451ea 100644
--- a/src/test/java/org/apache/sling/feature/io/json/FeatureJSONWriterTest.java
+++ b/src/test/java/org/apache/sling/feature/io/json/FeatureJSONWriterTest.java
@@ -16,17 +16,14 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.io.json.FeatureJSONReader;
-import org.apache.sling.feature.io.json.FeatureJSONWriter;
-import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
-import org.junit.Test;
+import static org.junit.Assert.assertEquals;
 
 import java.io.StringReader;
 import java.io.StringWriter;
 import java.util.Arrays;
 
-import static org.junit.Assert.assertEquals;
+import org.apache.sling.feature.Feature;
+import org.junit.Test;
 
 public class FeatureJSONWriterTest {
 
@@ -36,7 +33,7 @@
         try ( final StringWriter writer = new StringWriter() ) {
             FeatureJSONWriter.write(writer, f);
             try ( final StringReader reader = new StringReader(writer.toString()) ) {
-                rf = FeatureJSONReader.read(reader, null, SubstituteVariables.RESOLVE);
+                rf = FeatureJSONReader.read(reader, null);
             }
         }
         assertEquals(f.getId(), rf.getId());
@@ -53,7 +50,7 @@
         try ( final StringWriter writer = new StringWriter() ) {
             FeatureJSONWriter.write(writer, f);
             try ( final StringReader reader = new StringReader(writer.toString()) ) {
-                rf = FeatureJSONReader.read(reader, null, SubstituteVariables.RESOLVE);
+                rf = FeatureJSONReader.read(reader, null);
             }
         }
 
diff --git a/src/test/java/org/apache/sling/feature/io/json/U.java b/src/test/java/org/apache/sling/feature/io/json/U.java
index 904f158..fa23b47 100644
--- a/src/test/java/org/apache/sling/feature/io/json/U.java
+++ b/src/test/java/org/apache/sling/feature/io/json/U.java
@@ -16,18 +16,16 @@
  */
 package org.apache.sling.feature.io.json;
 
-import org.apache.sling.feature.Configuration;
-import org.apache.sling.feature.Feature;
-import org.apache.sling.feature.io.json.FeatureJSONReader;
-import org.apache.sling.feature.io.json.FeatureJSONReader.SubstituteVariables;
-import org.osgi.resource.Capability;
-import org.osgi.resource.Requirement;
+import static org.junit.Assert.fail;
 
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.util.List;
 
-import static org.junit.Assert.fail;
+import org.apache.sling.feature.Configuration;
+import org.apache.sling.feature.Feature;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
 
 /** Test utilities */
 public class U {
@@ -35,13 +33,9 @@
     /** Read the feature from the provided resource
      */
     public static Feature readFeature(final String name) throws Exception {
-        return readFeature(name, SubstituteVariables.RESOLVE);
-    }
-
-    public static Feature readFeature(final String name, final SubstituteVariables phase) throws Exception {
         try ( final Reader reader = new InputStreamReader(U.class.getResourceAsStream("/features/" + name + ".json"),
                 "UTF-8") ) {
-            return FeatureJSONReader.read(reader, name, phase);
+            return FeatureJSONReader.read(reader, name);
         }
     }
 
diff --git a/src/test/resources/applications/app_with_vars.json b/src/test/resources/applications/app_with_vars.json
deleted file mode 100644
index 86ff7c8..0000000
--- a/src/test/resources/applications/app_with_vars.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-  "frameworkId":"org.apache.felix:org.apache.felix.framework:6.0.0",
-  "variables":{
-    "web.port":"8888"
-  },
-  "features":[
-    "org.apache.sling:mytestfeature:slingfeature:test:0.0.1-SNAPSHOT"
-  ],
-  "bundles":[
-    {
-      "id":"org.apache.felix:org.apache.felix.log:1.0.1",
-      "start-level":"1",
-      "start-order":"1"
-    }
-  ],
-  "configurations":{
-    "org.apache.felix.http":{
-      "org.osgi.service.http.port":"${web.port}"
-    }
-  },
-  "framework-properties":{
-    "org.osgi.framework.bootdelegation":"sun.*,com.sun.*"
-  }
-}
\ No newline at end of file