You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by gn...@apache.org on 2017/04/11 10:18:11 UTC

[1/4] karaf git commit: [KARAF-5082] Support external configurations

Repository: karaf
Updated Branches:
  refs/heads/master b3572bff5 -> 9f89d2c72


[KARAF-5082] Support external configurations

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/9f89d2c7
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/9f89d2c7
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/9f89d2c7

Branch: refs/heads/master
Commit: 9f89d2c723f7022d9fd79c52cca8f65b4942602e
Parents: 02dede3
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Apr 10 17:43:31 2017 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Apr 11 12:17:52 2017 +0200

----------------------------------------------------------------------
 .../org/apache/karaf/features/ConfigInfo.java   |   2 +
 .../karaf/features/FeaturesNamespaces.java      |   6 +-
 .../karaf/features/internal/model/Config.java   |  10 +
 .../karaf/features/internal/model/JaxbUtil.java |   3 +
 .../service/FeatureConfigInstaller.java         |   6 +-
 .../karaf/features/karaf-features-1.5.0.xsd     | 337 +++++++++++++++++++
 .../apache/karaf/itests/ExternalConfigTest.java |  79 +++++
 .../assembly/AssemblyDeployCallback.java        |  50 +--
 .../apache/karaf/profile/assembly/Builder.java  |  62 ++--
 .../src/it/test-aggregate-features/control.xml  |   2 +-
 .../src/it/test-basic-generation/control.xml    |   2 +-
 .../test-check-dependencies-failure/control.xml |   2 +-
 .../src/it/test-check-dependencies/control.xml  |   2 +-
 .../it/test-feature-dependencies/control.xml    |   2 +-
 .../test-feature-use-base-version/control.xml   |   2 +-
 .../control.xml                                 |   2 +-
 .../control.xml                                 |   2 +-
 .../test-feature-use-version-range/control.xml  |   2 +-
 .../test-include-project-artifact/control.xml   |   2 +-
 .../src/it/test-input-file/control.xml          |   2 +-
 .../src/it/test-rename-main-feature/control.xml |   2 +-
 .../it/test-repository-dependencies/control.xml |   2 +-
 .../src/it/test-simplify-bundles/control.xml    |   2 +-
 .../src/it/test-type-classifier/control.xml     |   2 +-
 24 files changed, 518 insertions(+), 67 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java b/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java
index f7b6b5c..76ea330 100644
--- a/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java
+++ b/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java
@@ -28,4 +28,6 @@ public interface ConfigInfo {
 
 	boolean isAppend();
 
+	boolean isExternal();
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java b/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
index 48237a8..a4fc589 100644
--- a/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
+++ b/features/core/src/main/java/org/apache/karaf/features/FeaturesNamespaces.java
@@ -30,8 +30,9 @@ public interface FeaturesNamespaces {
 	String URI_1_2_1 = "http://karaf.apache.org/xmlns/features/v1.2.1";
     String URI_1_3_0 = "http://karaf.apache.org/xmlns/features/v1.3.0";
     String URI_1_4_0 = "http://karaf.apache.org/xmlns/features/v1.4.0";
+    String URI_1_5_0 = "http://karaf.apache.org/xmlns/features/v1.5.0";
 
-    String URI_CURRENT = URI_1_4_0;
+    String URI_CURRENT = URI_1_5_0;
 
     QName FEATURES_0_0_0 = new QName("features");
     QName FEATURES_1_0_0 = new QName(URI_1_0_0, "features");
@@ -40,7 +41,8 @@ public interface FeaturesNamespaces {
 	QName FEATURES_1_2_1 = new QName(URI_1_2_1, "features");
     QName FEATURES_1_3_0 = new QName(URI_1_3_0, "features");
     QName FEATURES_1_4_0 = new QName(URI_1_4_0, "features");
+    QName FEATURES_1_5_0 = new QName(URI_1_5_0, "features");
 
-    QName FEATURES_CURRENT = FEATURES_1_4_0;
+    QName FEATURES_CURRENT = FEATURES_1_5_0;
 
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/java/org/apache/karaf/features/internal/model/Config.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/Config.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/Config.java
index 8fed515..167de33 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/Config.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/Config.java
@@ -53,6 +53,8 @@ public class Config implements ConfigInfo {
     protected String name;
     @XmlAttribute(required = false)
 	private Boolean append = false;
+    @XmlAttribute
+	private Boolean external = false;
 
     /**
      * Gets the value of the value property.
@@ -108,6 +110,14 @@ public class Config implements ConfigInfo {
 		this.append = append;
 	}
 
+	public boolean isExternal() {
+		return external;
+	}
+
+	public void setExternal(boolean external) {
+		this.external = external;
+	}
+
 	public Properties getProperties() {
 		Properties props = new Properties();
 		try {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java b/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
index 6c63d16..ef307f4 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/model/JaxbUtil.java
@@ -164,6 +164,9 @@ public final class JaxbUtil {
             case FeaturesNamespaces.URI_1_4_0:
                 schemaLocation = "/org/apache/karaf/features/karaf-features-1.4.0.xsd";
                 break;
+            case FeaturesNamespaces.URI_1_5_0:
+                schemaLocation = "/org/apache/karaf/features/karaf-features-1.5.0.xsd";
+                break;
             default:
                 throw new IllegalArgumentException("Unsupported namespace: " + namespace);
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
index c95104c..92700b5 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
@@ -104,7 +104,11 @@ public class FeatureConfigInstaller {
             TypedProperties props = new TypedProperties();
             // trim lines
             String val = config.getValue();
-            props.load(new StringReader(val));
+            if (config.isExternal()) {
+                props.load(new URL(val));
+            } else {
+                props.load(new StringReader(val));
+            }
 			String[] pid = parsePid(config.getName());
 			Configuration cfg = findExistingConfiguration(configAdmin, pid[0], pid[1]);
 			if (cfg == null) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.5.0.xsd
----------------------------------------------------------------------
diff --git a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.5.0.xsd b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.5.0.xsd
new file mode 100644
index 0000000..a970576
--- /dev/null
+++ b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.5.0.xsd
@@ -0,0 +1,337 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    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.
+
+-->
+<xs:schema elementFormDefault="qualified"
+    targetNamespace="http://karaf.apache.org/xmlns/features/v1.5.0"
+    xmlns:tns="http://karaf.apache.org/xmlns/features/v1.5.0"
+    xmlns:xs="http://www.w3.org/2001/XMLSchema">
+
+    <xs:annotation>
+        <xs:documentation><![CDATA[
+Karaf features mechanism. For documentation please visit the
+<a href="http://karaf.apache.org/">Karaf website</a>.
+        ]]></xs:documentation>
+    </xs:annotation>
+
+    <xs:complexType name="features">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Root element of Feature definition. It contains an required attribute for
+designating from which repository this feature should be loaded. The Karaf
+shell will show the repository name when displaying information about the feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="repository" type="xs:anyURI">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+Additional feature repository where dependencies are stored.
+                    ]]></xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="resource-repository" type="xs:anyURI">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+Additional OSGi Resource Repository where OSGi resources are stored.
+                    ]]></xs:documentation>
+                </xs:annotation>
+            </xs:element>
+            <xs:element name="feature" type="tns:feature">
+                <xs:annotation>
+                    <xs:documentation><![CDATA[
+Feature definition.
+                    ]]></xs:documentation>
+                </xs:annotation>
+            </xs:element>
+        </xs:choice>
+        <xs:attribute name="name" type="xs:string" use="required"/>
+    </xs:complexType>
+
+    <xs:complexType name="feature">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Definition of the Feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:sequence>
+            <xs:choice minOccurs="0" maxOccurs="unbounded">
+                <xs:element name="details" minOccurs="0" type="xs:string">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+    The help text shown for this feature when using feature:info console command.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:element>
+                <xs:element name="config" type="tns:config" />
+                <xs:element name="configfile" type="tns:configFile" />
+                <xs:element name="feature" type="tns:dependency" />
+                <xs:element name="bundle" type="tns:bundle" />
+                <xs:element name="conditional" type="tns:conditional" />
+                <xs:element name="requirement" type="tns:requirement" />
+                <xs:element name="capability" type="tns:capability" />
+                <xs:element name="library" type="tns:library" />
+            </xs:choice>
+            <xs:element name="scoping" minOccurs="0" maxOccurs="1" type="tns:scoping" />
+        </xs:sequence>
+        <xs:attribute name="name" type="tns:featureName" use="required" />
+        <xs:attribute name="version" type="xs:string" default="0.0.0" />
+        <xs:attribute name="description" type="xs:string" />
+        <xs:attribute name="install" type="tns:install">
+            <xs:annotation>
+                <xs:documentation><![CDATA[
+Marks if the feaute will be automatically started when thrown to the deploy folder.
+                ]]>
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <xs:attribute name="start-level" type="xs:int">
+             <xs:annotation>
+                <xs:documentation><![CDATA[
+Set this attribute to have an OSGi start level for this feature different
+from the default start level defined in Karaf's config.properties.
+                ]]>
+                </xs:documentation>
+             </xs:annotation>
+         </xs:attribute>
+        <xs:attribute name="hidden" type="xs:boolean" default="false" />
+    </xs:complexType>
+
+    <xs:complexType name="conditional">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Definition of the Conditional.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="config" type="tns:config" />
+            <xs:element name="configfile" type="tns:configFile" />
+            <xs:element name="feature" type="tns:dependency" />
+            <xs:element name="bundle" type="tns:bundle" />
+            <xs:element name="condition" type="tns:dependency" minOccurs="0" maxOccurs="1" />
+        </xs:choice>
+    </xs:complexType>
+
+
+    <xs:complexType name="bundle">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Deployable element to install.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:anyURI">
+                <xs:attribute name="start-level" type="xs:int">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Set this attribute to have an OSGi start level for this bundle different
+from the default start level defined in the Karaf's config.properties.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="start" type="xs:boolean" default="true">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+If false, leaves bundle in resolved state rather than the default active state.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="dependency" type="xs:boolean">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Mark this bundle as a dependency for the resolver.
+                        ]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="dependency">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Dependency of feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="tns:featureName">
+                <xs:attribute name="version" type="xs:string" default="0.0.0" />
+                <xs:attribute name="prerequisite" type="xs:boolean" default="false"/>
+                <xs:attribute name="dependency" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="config">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Configuration entries which should be created during feature installation. This
+configuration may be used with OSGi Configuration Admin. The element content is
+read in as a properties file.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="name" type="xs:string" use="required" />
+                <xs:attribute name="append" type="xs:boolean" use="optional">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Optional flag to append unknown values to the configuration.                 		
+                		]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="external" type="xs:boolean" use="optional">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+Specifies that this configuration is external, i.e. the content of this element
+points to the URL where the configuration can be downloaded from.
+                		]]>
+                        </xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="configFile">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional configuration files which should be created during feature installation.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:anyURI">
+                <xs:attribute name="finalname" type="xs:string" use="required">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+The final destination path and name for the configuration file.
+                        ]]></xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+                <xs:attribute name="override" type="xs:boolean">
+                    <xs:annotation>
+                        <xs:documentation><![CDATA[
+If the configFile already exists at the finalname location, whether or not to replace it.
+                        ]]></xs:documentation>
+                    </xs:annotation>
+                </xs:attribute>
+
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="requirement">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional requirements of this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="prerequisite" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="capability">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional capability of this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:complexType name="library">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Additional capability of this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="type" type="tns:libraryType" default="default"/>
+                <xs:attribute name="export" type="xs:boolean" default="false"/>
+                <xs:attribute name="delegate" type="xs:boolean" default="false"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="libraryType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default"/>
+            <xs:enumeration value="endorsed"/>
+            <xs:enumeration value="extension"/>
+            <xs:enumeration value="boot"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:complexType name="scoping">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Scoping definition for this feature.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:choice minOccurs="0" maxOccurs="unbounded">
+            <xs:element name="import" type="tns:scopeFilter"/>
+            <xs:element name="export" type="tns:scopeFilter"/>
+        </xs:choice>
+        <xs:attribute name="acceptDependencies" type="xs:boolean"/>
+    </xs:complexType>
+
+    <xs:complexType name="scopeFilter">
+        <xs:simpleContent>
+            <xs:extension base="xs:string">
+                <xs:attribute name="namespace"  type="xs:string" use="required"/>
+            </xs:extension>
+        </xs:simpleContent>
+    </xs:complexType>
+
+    <xs:simpleType name="featureName">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Feature name should be non empty string.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1" />
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="install">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Installation mode. Can be either manual or auto. Specifies whether the feature should be automatically installed when
+dropped inside the deploy folder. Note: This attribute doesn't affect feature descriptors that are installed from the
+command line or as part of the org.apache.karaf.features.cfg.
+            ]]></xs:documentation>
+        </xs:annotation>
+        <xs:restriction base="xs:string">
+            <xs:minLength value="1" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:element name="features" type="tns:features" />
+
+</xs:schema>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/itests/src/test/java/org/apache/karaf/itests/ExternalConfigTest.java
----------------------------------------------------------------------
diff --git a/itests/src/test/java/org/apache/karaf/itests/ExternalConfigTest.java b/itests/src/test/java/org/apache/karaf/itests/ExternalConfigTest.java
new file mode 100644
index 0000000..468a92f
--- /dev/null
+++ b/itests/src/test/java/org/apache/karaf/itests/ExternalConfigTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.itests;
+
+import org.apache.karaf.features.FeaturesService;
+import org.apache.sshd.common.util.io.IoUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.service.cm.Configuration;
+import org.osgi.service.cm.ConfigurationAdmin;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectName;
+import java.io.BufferedWriter;
+import java.io.InputStream;
+import java.io.Writer;
+import java.lang.management.ManagementFactory;
+import java.net.URI;
+import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.OpenOption;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class ExternalConfigTest extends KarafTestSupport {
+
+    @Test
+    public void externalConfigTest() throws Exception {
+        Path dir = Paths.get(System.getProperty("karaf.base"), "system/org/foo/bar/1.0-SNAPSHOT");
+        Files.createDirectories(dir);
+        try (BufferedWriter w = Files.newBufferedWriter(dir.resolve("bar-1.0-SNAPSHOT.properties"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+            w.write("key=value");
+        }
+        try (BufferedWriter w = Files.newBufferedWriter(dir.resolve("bar-1.0-SNAPSHOT-features.xml"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE)) {
+            w.write("<features name='org.foo'>\n" +
+                    "  <feature name='bar' version='1.0-SNAPSHOT'>\n" +
+                    "    <config name='org.foo' external='true'>\n" +
+                    "      mvn:org.foo/bar/1.0-SNAPSHOT/properties" +
+                    "    </config>\n" +
+                    "  </feature>\n" +
+                    "</features>\n");
+        }
+
+        ConfigurationAdmin ca = getOsgiService(ConfigurationAdmin.class);
+
+        Configuration[] cfgs = ca.listConfigurations("(service.pid=org.foo)");
+        assertNull(cfgs);
+
+        getOsgiService(FeaturesService.class)
+                .addRepository(URI.create("mvn:org.foo/bar/1.0-SNAPSHOT/xml/features"), true);
+
+        cfgs = ca.listConfigurations("(service.pid=org.foo)");
+        assertNotNull(cfgs);
+        assertEquals(1, cfgs.length);
+        assertEquals("value", cfgs[0].getProperties().get("key"));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
index 026bf10..cce511f 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/AssemblyDeployCallback.java
@@ -136,30 +136,42 @@ public class AssemblyDeployCallback implements Deployer.DeployCallback {
             }
         }
         // Install
+        Downloader downloader = manager.createDownloader();
         for (Config config : ((Feature) feature).getConfig()) {
-            Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
-            LOGGER.info("      adding config file: {}", homeDirectory.relativize(configFile));
-            if (!Files.exists(configFile)) {
-                Files.write(configFile, config.getValue().getBytes());
-            } else if (config.isAppend()) {
-                Files.write(configFile, config.getValue().getBytes(), StandardOpenOption.APPEND);
+            if (config.isExternal()) {
+                downloader.download(config.getValue().trim(), provider -> {
+                    Path input = provider.getFile().toPath();
+                    byte[] data = Files.readAllBytes(input);
+                    Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
+                    LOGGER.info("      adding config file: {}", homeDirectory.relativize(configFile));
+                    if (!Files.exists(configFile)) {
+                        Files.write(configFile, data);
+                    } else if (config.isAppend()) {
+                        Files.write(configFile, data, StandardOpenOption.APPEND);
+                    }
+                });
+            } else {
+                byte[] data = config.getValue().getBytes();
+                Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
+                LOGGER.info("      adding config file: {}", homeDirectory.relativize(configFile));
+                if (!Files.exists(configFile)) {
+                    Files.write(configFile, data);
+                } else if (config.isAppend()) {
+                    Files.write(configFile, data, StandardOpenOption.APPEND);
+                }
             }
         }
-        Downloader downloader = manager.createDownloader();
         for (final ConfigFile configFile : ((Feature) feature).getConfigfile()) {
-            downloader.download(configFile.getLocation(), new DownloadCallback() {
-                @Override
-                public void downloaded(StreamProvider provider) throws Exception {
-                    Path input = provider.getFile().toPath();
-                    String path = configFile.getFinalname();
-                    if (path.startsWith("/")) {
-                        path = path.substring(1);
-                    }
-                    path = substFinalName(path);
-                    Path output = homeDirectory.resolve(path);
-                    LOGGER.info("      adding config file: {}", path);
-                    Files.copy(input, output, StandardCopyOption.REPLACE_EXISTING);
+            downloader.download(configFile.getLocation(), provider -> {
+                Path input = provider.getFile().toPath();
+                String path = configFile.getFinalname();
+                if (path.startsWith("/")) {
+                    path = path.substring(1);
                 }
+                path = substFinalName(path);
+                Path output = homeDirectory.resolve(path);
+                LOGGER.info("      adding config file: {}", path);
+                Files.copy(input, output, StandardCopyOption.REPLACE_EXISTING);
             });
         }
         List<String> libraries = new ArrayList<>();

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
----------------------------------------------------------------------
diff --git a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
index 1b57729..371a18f 100644
--- a/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
+++ b/profile/src/main/java/org/apache/karaf/profile/assembly/Builder.java
@@ -68,6 +68,7 @@ import org.apache.karaf.features.internal.model.Bundle;
 import org.apache.karaf.features.internal.model.Conditional;
 import org.apache.karaf.features.internal.model.Config;
 import org.apache.karaf.features.internal.model.ConfigFile;
+import org.apache.karaf.features.internal.model.Content;
 import org.apache.karaf.features.internal.model.Dependency;
 import org.apache.karaf.features.internal.model.Feature;
 import org.apache.karaf.features.internal.model.Features;
@@ -906,29 +907,33 @@ public class Builder {
                     }
                 }
             }
-            // Install config files
-            for (ConfigFile configFile : feature.getConfigfile()) {
-                installArtifact(downloader, configFile.getLocation().trim());
-            }
-            for (Conditional cond : feature.getConditional()) {
-                for (ConfigFile configFile : cond.getConfigfile()) {
-                    installArtifact(downloader, configFile.getLocation().trim());
+            List<Content> contents = new ArrayList<>();
+            contents.add(feature);
+            contents.addAll(feature.getConditional());
+            for (Content content : contents) {
+                // Install config files
+                for (Config config : content.getConfig()) {
+                    if (config.isExternal()) {
+                        installArtifact(downloader, config.getValue().trim());
+                    }
                 }
-            }
-            // Extract configs
-            for (Config config : feature.getConfig()) {
-                if (pidMatching(config.getName())) {
-                    Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
-                    LOGGER.info("      adding config file: {}", homeDirectory.relativize(configFile));
-                    Files.write(configFile, config.getValue().getBytes());
+                for (ConfigFile configFile : content.getConfigfile()) {
+                    installArtifact(downloader, configFile.getLocation().trim());
                 }
-            }
-            for (Conditional cond : feature.getConditional()) {
-                for (Config config : cond.getConfig()) {
+                // Extract configs
+                for (Config config : content.getConfig()) {
                     if (pidMatching(config.getName())) {
                         Path configFile = etcDirectory.resolve(config.getName() + ".cfg");
                         LOGGER.info("      adding config file: {}", homeDirectory.relativize(configFile));
-                        Files.write(configFile, config.getValue().getBytes());
+                        if (config.isExternal()) {
+                            downloader.download(config.getValue().trim(), provider -> {
+                                synchronized (provider) {
+                                    Files.copy(provider.getFile().toPath(), configFile, StandardCopyOption.REPLACE_EXISTING);
+                                }
+                            });
+                        } else {
+                            Files.write(configFile, config.getValue().getBytes());
+                        }
                     }
                 }
             }
@@ -1224,18 +1229,15 @@ public class Builder {
                 // for bad formed URL (like in Camel for mustache-compiler), we remove the trailing /
                 location = location.substring(0, location.length() - 1);
             }
-            downloader.download(location, new DownloadCallback() {
-                @Override
-                public void downloaded(final StreamProvider provider) throws Exception {
-                    String uri = provider.getUrl();
-                    if (Blacklist.isBundleBlacklisted(blacklistedBundles, uri)) {
-                        throw new RuntimeException("Bundle " + uri + " is blacklisted");
-                    }
-                    Path path = pathFromProviderUrl(uri);
-                    synchronized (provider) {
-                        Files.createDirectories(path.getParent());
-                        Files.copy(provider.getFile().toPath(), path, StandardCopyOption.REPLACE_EXISTING);
-                    }
+            downloader.download(location, provider -> {
+                String uri = provider.getUrl();
+                if (Blacklist.isBundleBlacklisted(blacklistedBundles, uri)) {
+                    throw new RuntimeException("Bundle " + uri + " is blacklisted");
+                }
+                Path path = pathFromProviderUrl(uri);
+                synchronized (provider) {
+                    Files.createDirectories(path.getParent());
+                    Files.copy(provider.getFile().toPath(), path, StandardCopyOption.REPLACE_EXISTING);
                 }
             });
         } else {

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
index 81e1844..1508054 100644
--- a/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-aggregate-features/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="aggregate-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="aggregate-features">
     <feature description="aggregate-recursive-module-c" version="1.0.0.SNAPSHOT" name="aggregate-recursive-module-c">
         <details>Test Description</details>
         <bundle>mvn:test/aggregate-recursive-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml b/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
index 1d4ca2a..b0cecdb 100644
--- a/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-basic-generation/control.xml
@@ -18,4 +18,4 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-basic-generation"/>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-basic-generation"/>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
index 9e7ef4e..d4b982b 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies-failure/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="check-dependencies-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="check-dependencies-features">
     <feature description="dependency-module-c" version="1.0.0.SNAPSHOT" name="dependency-module-c">
         <bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
         <bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
index 9e7ef4e..d4b982b 100644
--- a/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-check-dependencies/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="check-dependencies-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="check-dependencies-features">
     <feature description="dependency-module-c" version="1.0.0.SNAPSHOT" name="dependency-module-c">
         <bundle>mvn:test/dependency-module-a/1.0-SNAPSHOT</bundle>
         <bundle>mvn:test/dependency-module-b/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-feature-dependencies/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-feature-dependencies/control.xml b/tooling/karaf-maven-plugin/src/it/test-feature-dependencies/control.xml
index d0f9b53..e859c84 100644
--- a/tooling/karaf-maven-plugin/src/it/test-feature-dependencies/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-feature-dependencies/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="dependencies-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="dependencies-features">
     <feature name="dependency-feature-a" description="dependency-feature-a" version="1.0.0.SNAPSHOT">
         <feature version="1.0.0.SNAPSHOT" prerequisite="false" dependency="false">dependency-feature-c</feature>
         <bundle>mvn:test/dependency-bundle-a/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-feature-use-base-version/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-feature-use-base-version/control.xml b/tooling/karaf-maven-plugin/src/it/test-feature-use-base-version/control.xml
index 60e46d6..8e357ed 100644
--- a/tooling/karaf-maven-plugin/src/it/test-feature-use-base-version/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-feature-use-base-version/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-feature-use-base-version">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-feature-use-base-version">
     <feature name="test-feature-use-base-version" description="test-feature-use-base-version" version="1.0.0.SNAPSHOT">
         <bundle>mvn:org.apache.commons/commons-lang3/3.4</bundle>
     </feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transfer-properties/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transfer-properties/control.xml b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transfer-properties/control.xml
index 131f23f..14a253c 100644
--- a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transfer-properties/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transfer-properties/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="feature">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="feature">
     <feature name="feature" description="feature" version="1.0.0.SNAPSHOT">
         <bundle>mvn:test/transitive/[0.9,1.9)</bundle>
         <bundle>mvn:org.apache.commons/commons-lang3/[3.0,3.4)</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transitive/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transitive/control.xml b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transitive/control.xml
index 131f23f..14a253c 100644
--- a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transitive/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range-transitive/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="feature">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="feature">
     <feature name="feature" description="feature" version="1.0.0.SNAPSHOT">
         <bundle>mvn:test/transitive/[0.9,1.9)</bundle>
         <bundle>mvn:org.apache.commons/commons-lang3/[3.0,3.4)</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range/control.xml b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range/control.xml
index a29d8c8..a3594a5 100644
--- a/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-feature-use-version-range/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-feature-use-version-range">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-feature-use-version-range">
     <feature name="test-feature-use-version-range" description="test-feature-use-version-range" version="1.0.0.SNAPSHOT">
         <bundle>mvn:org.apache.commons/commons-lang3/[3.0,3.4)</bundle>
     </feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-include-project-artifact/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-include-project-artifact/control.xml b/tooling/karaf-maven-plugin/src/it/test-include-project-artifact/control.xml
index caf5960..fe1b017 100644
--- a/tooling/karaf-maven-plugin/src/it/test-include-project-artifact/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-include-project-artifact/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-include-project-artifact">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-include-project-artifact">
     <feature name="test-include-project-artifact" description="test-include-project-artifact" version="1.0.0.SNAPSHOT">
         <bundle>mvn:test/test-include-project-artifact/1.0-SNAPSHOT</bundle>
     </feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
index b881b51..7ec72f0 100644
--- a/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-input-file/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-input-file">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-input-file">
     <feature description="Test Description" version="1.0.0.SNAPSHOT" name="test-input-file">
         <details>Test Description</details>
         <bundle>mvn:test/test-input-file/1.0-SNAPSHOT</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-rename-main-feature/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-rename-main-feature/control.xml b/tooling/karaf-maven-plugin/src/it/test-rename-main-feature/control.xml
index c624790..46d0824 100644
--- a/tooling/karaf-maven-plugin/src/it/test-rename-main-feature/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-rename-main-feature/control.xml
@@ -19,7 +19,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-rename-project-artifact">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-rename-project-artifact">
     <feature name="test-feature" description="test-rename-project-artifact" version="1.0.0.SNAPSHOT">
         <bundle>mvn:test/test-rename-project-artifact/1.0-SNAPSHOT</bundle>
     </feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-repository-dependencies/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-repository-dependencies/control.xml b/tooling/karaf-maven-plugin/src/it/test-repository-dependencies/control.xml
index 0cde6c9..276b97d 100644
--- a/tooling/karaf-maven-plugin/src/it/test-repository-dependencies/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-repository-dependencies/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="dependency-feature-a">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="dependency-feature-a">
     <repository>mvn:test/dependency-feature-b/1.0-SNAPSHOT/xml/features</repository>
     <feature name="dependency-feature-a" description="dependency-feature-a" version="1.0.0.SNAPSHOT">
         <feature version="1.0.0.SNAPSHOT" prerequisite="false" dependency="false">dependency-feature-b</feature>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-simplify-bundles/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-simplify-bundles/control.xml b/tooling/karaf-maven-plugin/src/it/test-simplify-bundles/control.xml
index 94cb36a..fdc84cf 100644
--- a/tooling/karaf-maven-plugin/src/it/test-simplify-bundles/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-simplify-bundles/control.xml
@@ -18,7 +18,7 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="simplify-features">
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="simplify-features">
     <repository>mvn:test/simplify-feature-a/1.0-SNAPSHOT/xml/features</repository>
     <feature name="simplify-feature-as-is" description="simplify-feature-as-is" version="1.0.0.SNAPSHOT">
         <details>Check that bundles are simplified</details>

http://git-wip-us.apache.org/repos/asf/karaf/blob/9f89d2c7/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
----------------------------------------------------------------------
diff --git a/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml b/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
index 246e2cf..3a7371c 100644
--- a/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
+++ b/tooling/karaf-maven-plugin/src/it/test-type-classifier/control.xml
@@ -18,4 +18,4 @@
   ~ under the License.
   -->
 
-<features xmlns="http://karaf.apache.org/xmlns/features/v1.4.0" name="test-type-classifier"/>
+<features xmlns="http://karaf.apache.org/xmlns/features/v1.5.0" name="test-type-classifier"/>


[2/4] karaf git commit: [KARAF-5074] Better support for typed configs for built-in features and bin/client

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/util/src/main/java/org/apache/karaf/util/locks/FileLockUtils.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/locks/FileLockUtils.java b/util/src/main/java/org/apache/karaf/util/locks/FileLockUtils.java
index 9eef129..e9455c2 100644
--- a/util/src/main/java/org/apache/karaf/util/locks/FileLockUtils.java
+++ b/util/src/main/java/org/apache/karaf/util/locks/FileLockUtils.java
@@ -24,92 +24,76 @@ import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
 import java.nio.channels.FileLock;
-import org.apache.felix.utils.properties.Properties;
+import org.apache.felix.utils.properties.TypedProperties;
 
 public final class FileLockUtils {
 
     private FileLockUtils() { }
 
-    public static interface Runnable {
-        void run(RandomAccessFile file) throws IOException;
+    public interface Runnable<T> {
+        void run(T file) throws IOException;
     }
 
-    public static interface Callable<T> {
-        T call(RandomAccessFile file) throws IOException;
+    public interface Callable<T, U> {
+        U call(T file) throws IOException;
     }
 
-    public static interface RunnableWithProperties {
-        void run(Properties properties) throws IOException;
-    }
-
-    public static interface CallableWithProperties<T> {
-        T call(Properties properties) throws IOException;
-    }
-
-    public static void execute(File file, Runnable callback) throws IOException {
-        RandomAccessFile raf = new RandomAccessFile(file, "rw");
-        try {
+    public static void execute(File file, Runnable<? super RandomAccessFile> callback) throws IOException {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             FileLock lock = raf.getChannel().lock();
             try {
                 callback.run(raf);
             } finally {
                 lock.release();
             }
-        } finally {
-            raf.close();
         }
     }
 
-    public static <T> T execute(File file, Callable<T> callback) throws IOException {
-        RandomAccessFile raf = new RandomAccessFile(file, "rw");
-        try {
+    public static <T> T execute(File file, Callable<? super RandomAccessFile, T> callback) throws IOException {
+        try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) {
             FileLock lock = raf.getChannel().lock();
-            
             try {
                 return callback.call(raf);
             } finally {
                 lock.release();
             }
-        } finally {
-            raf.close();
         }
     }
 
-    public static void execute(File file, final RunnableWithProperties callback, final boolean writeToFile) throws IOException {
-        execute(file, new Runnable() {
-            public void run(RandomAccessFile file) throws IOException {
-                byte[] buffer = new byte[(int) file.length()];
-                file.readFully(buffer);
-                Properties props = new Properties();
-                props.load(new ByteArrayInputStream(buffer));
-                callback.run(props);
-                if (writeToFile) {
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                    props.store(baos, null);
-                    file.setLength(0);
-                    file.write(baos.toByteArray());
-                }
+    public static void execute(File file, Runnable<? super TypedProperties> callback, boolean writeToFile) throws IOException {
+        execute(file, raf -> {
+            TypedProperties props = load(raf);
+            callback.run(props);
+            if (writeToFile) {
+                save(props, raf);
             }
         });
     }
 
-    public static <T> T execute(File file, final CallableWithProperties<T> callback, final boolean writeToFile) throws IOException {
-        return execute(file, new Callable<T>() {
-            public T call(RandomAccessFile file) throws IOException {
-                byte[] buffer = new byte[(int) file.length()];
-                file.readFully(buffer);
-                Properties props = new Properties();
-                props.load(new ByteArrayInputStream(buffer));
-                T result = callback.call(props);
-                if (writeToFile) {
-                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
-                    props.store(baos, null);
-                    file.setLength(0);
-                    file.write(baos.toByteArray());
-                }
-                return result;
+    public static <T> T execute(File file, Callable<? super TypedProperties, T> callback, boolean writeToFile) throws IOException {
+        return execute(file, raf -> {
+            TypedProperties props = load(raf);
+            T result = callback.call(props);
+            if (writeToFile) {
+                save(props, raf);
             }
+            return result;
         });
     }
 
+    private static TypedProperties load(RandomAccessFile raf) throws IOException {
+        byte[] buffer = new byte[(int) raf.length()];
+        raf.readFully(buffer);
+        TypedProperties props = new TypedProperties();
+        props.load(new ByteArrayInputStream(buffer));
+        return props;
+    }
+
+    private static void save(TypedProperties props, RandomAccessFile raf) throws IOException {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        props.save(baos);
+        raf.setLength(0);
+        raf.write(baos.toByteArray());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
index 670ff9d..c1b00da 100644
--- a/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
+++ b/util/src/main/java/org/apache/karaf/util/tracker/BaseActivator.java
@@ -32,6 +32,8 @@ import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import org.osgi.framework.BundleActivator;
 import org.osgi.framework.BundleContext;
@@ -228,6 +230,28 @@ public class BaseActivator implements BundleActivator, Runnable {
         return def;
     }
 
+    protected String[] getStringArray(String key, String def) {
+        Object val = null;
+        if (configuration != null) {
+            val = configuration.get(key);
+        }
+        if (val == null) {
+            val = def;
+        }
+        if (val == null) {
+            return null;
+        }
+        Stream<String> s;
+        if (val instanceof String[]) {
+            return (String[]) val;
+        } else if (val instanceof Iterable) {
+            return StreamSupport.stream(((Iterable<?>) val).spliterator(), false)
+                    .map(Object::toString).toArray(String[]::new);
+        } else {
+            return val.toString().split(",");
+        }
+    }
+
     protected void reconfigure() {
         if (scheduled.compareAndSet(false, true)) {
             executor.submit(this);


[3/4] karaf git commit: [KARAF-5074] Better support for typed configs for built-in features and bin/client

Posted by gn...@apache.org.
http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
----------------------------------------------------------------------
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
index c5a6e19..0d30b19 100644
--- a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -20,7 +20,6 @@ import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
-import java.io.FilenameFilter;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -33,15 +32,16 @@ import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.ArrayList;
-import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Properties;
 import java.util.Scanner;
 import java.util.TreeMap;
 
+import org.apache.felix.utils.properties.InterpolationHelper;
+import org.apache.felix.utils.properties.Properties;
+import org.apache.felix.utils.properties.TypedProperties;
 import org.apache.karaf.instance.core.Instance;
 import org.apache.karaf.instance.core.InstanceService;
 import org.apache.karaf.instance.core.InstanceSettings;
@@ -54,6 +54,7 @@ import org.apache.karaf.profile.ProfileBuilder;
 import org.apache.karaf.profile.ProfileService;
 import org.apache.karaf.shell.support.ansi.SimpleAnsi;
 import org.apache.karaf.util.StreamUtils;
+import org.apache.karaf.util.config.PropertiesLoader;
 import org.apache.karaf.util.locks.FileLockUtils;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.FrameworkUtil;
@@ -63,10 +64,10 @@ import org.slf4j.LoggerFactory;
 
 public class InstanceServiceImpl implements InstanceService {
 
-    private static final String RESOURCE_BASE = "org/apache/karaf/instance/resources/";
     public static final String STORAGE_FILE = "instance.properties";
     public static final String BACKUP_EXTENSION = ".bak";
     private static final String FEATURES_CFG = "etc/org.apache.karaf.features.cfg";
+    private static final String RESOURCE_BASE = "org/apache/karaf/instance/resources/";
 
     private static final Logger LOGGER = LoggerFactory.getLogger(InstanceServiceImpl.class);
 
@@ -86,7 +87,7 @@ public class InstanceServiceImpl implements InstanceService {
 
     public static final String DEFAULT_JAVA_OPTS = "-server -Xmx512M -Dcom.sun.management.jmxremote -XX:+UnlockDiagnosticVMOptions -XX:+UnsyncloadClass";
 
-    private LinkedHashMap<String, InstanceImpl> proxies = new LinkedHashMap<String, InstanceImpl>();
+    private LinkedHashMap<String, InstanceImpl> proxies = new LinkedHashMap<>();
 
     private File storageLocation;
 
@@ -108,13 +109,13 @@ public class InstanceServiceImpl implements InstanceService {
         public State() {
             //read port start value from the root instance configuration
             try {
-                Properties shellProperty = new Properties();
-                shellProperty.load(new FileInputStream(new File(System.getProperty("karaf.etc"), "org.apache.karaf.shell.cfg")));
-                defaultSshPortStart = Integer.valueOf((String)shellProperty.getOrDefault("sshPort", 8101));
-                Properties managementProperty = new Properties();
-                managementProperty.load(new FileInputStream(new File(System.getProperty("karaf.etc"), "org.apache.karaf.management.cfg")));
-                defaultRmiRegistryPortStart = Integer.valueOf((String)managementProperty.getOrDefault("rmiRegistryPort", 1099));
-                defaultRmiServerPortStart = Integer.valueOf((String)managementProperty.getOrDefault("rmiServerPort", 1099));
+                TypedProperties shellProperty = new TypedProperties();
+                shellProperty.load(new File(System.getProperty("karaf.etc"), "org.apache.karaf.shell.cfg"));
+                defaultSshPortStart = getInt(shellProperty,"sshPort", 8101);
+                TypedProperties managementProperty = new TypedProperties();
+                managementProperty.load(new File(System.getProperty("karaf.etc"), "org.apache.karaf.management.cfg"));
+                defaultRmiRegistryPortStart = getInt(managementProperty, "rmiRegistryPort", 1099);
+                defaultRmiServerPortStart = getInt(managementProperty, "rmiServerPort", 1099);
             } catch (Exception e) {
                 LOGGER.debug("Could not read port start value from the root instance configuration.", e);
             }
@@ -146,13 +147,13 @@ public class InstanceServiceImpl implements InstanceService {
         this.stopTimeout = stopTimeout;
     }
 
-    private State loadData(org.apache.felix.utils.properties.Properties storage) {
+    private State loadData(TypedProperties storage) {
         State state = new State();
         int count = getInt(storage, "count", 0);
         state.defaultSshPortStart = getInt(storage, "ssh.port", state.defaultSshPortStart);
         state.defaultRmiRegistryPortStart = getInt(storage, "rmi.registry.port", state.defaultRmiRegistryPortStart);
         state.defaultRmiServerPortStart = getInt(storage, "rmi.server.port", state.defaultRmiServerPortStart);
-        state.instances = new LinkedHashMap<String, InstanceState>();
+        state.instances = new LinkedHashMap<>();
 
         for (int i = 0; i < count; i++) {
             InstanceState instance = new InstanceState();
@@ -169,7 +170,7 @@ public class InstanceServiceImpl implements InstanceService {
                 proxies.put(instance.name, new InstanceImpl(this, instance.name));
             }
         }
-        List<String> names = new ArrayList<String>(this.proxies.keySet());
+        List<String> names = new ArrayList<>(this.proxies.keySet());
         for (String name : names) {
             if (!state.instances.containsKey(name)) {
                 this.proxies.remove(name);
@@ -178,7 +179,7 @@ public class InstanceServiceImpl implements InstanceService {
         return state;
     }
 
-    private void saveData(State state, org.apache.felix.utils.properties.Properties storage) {
+    private void saveData(State state, TypedProperties storage) {
         storage.put("ssh.port", Integer.toString(state.defaultSshPortStart));
         storage.put("rmi.registry.port", Integer.toString(state.defaultRmiRegistryPortStart));
         storage.put("rmi.server.port", Integer.toString(state.defaultRmiServerPortStart));
@@ -202,34 +203,38 @@ public class InstanceServiceImpl implements InstanceService {
         }
     }
 
-    private boolean getBool(org.apache.felix.utils.properties.Properties storage, String name, boolean def) {
+    private static boolean getBool(TypedProperties storage, String name, boolean def) {
         Object value = storage.get(name);
-        if (value != null) {
+        if (value instanceof Boolean) {
+            return (Boolean) value;
+        } else if (value != null) {
             return Boolean.parseBoolean(value.toString());
         } else {
             return def;
         }
     }
 
-    private int getInt(org.apache.felix.utils.properties.Properties storage, String name, int def) {
+    private static int getInt(TypedProperties storage, String name, int def) {
         Object value = storage.get(name);
-        if (value != null) {
+        if (value instanceof Number) {
+            return ((Number) value).intValue();
+        } else if (value != null) {
             return Integer.parseInt(value.toString());
         } else {
             return def;
         }
     }
 
-    private String getString(org.apache.felix.utils.properties.Properties storage, String name, String def) {
+    private static String getString(TypedProperties storage, String name, String def) {
         Object value = storage.get(name);
         return value != null ? value.toString() : def;
     }
 
-    interface Task<T> {
-        T call(State state) throws IOException;
+    interface Task<U, T> {
+        T call(U state) throws IOException;
     }
 
-    synchronized <T> T execute(final Task<T> callback, final boolean writeToFile) {
+    synchronized <T> T execute(final Task<State, T> callback, final boolean writeToFile) {
         final File storageFile = new File(storageLocation, STORAGE_FILE);
         if (!storageFile.exists()) {
             storageFile.getParentFile().mkdirs();
@@ -244,15 +249,13 @@ public class InstanceServiceImpl implements InstanceService {
                 throw new IllegalStateException("Instance storage location should be a file: " + storageFile);
             }
             try {
-                return FileLockUtils.execute(storageFile, new FileLockUtils.CallableWithProperties<T>() {
-                    public T call(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                        State state = loadData(properties);
-                        T t = callback.call(state);
-                        if (writeToFile) {
-                            saveData(state, properties);
-                        }
-                        return t;
+                return FileLockUtils.execute(storageFile, properties -> {
+                    State state = loadData(properties);
+                    T t = callback.call(state);
+                    if (writeToFile) {
+                        saveData(state, properties);
                     }
+                    return t;
                 }, writeToFile);
             } catch (IOException e) {
                 throw new RuntimeException(e);
@@ -283,154 +286,150 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     public synchronized Instance createInstance(final String name, final InstanceSettings settings, final boolean printOutput) throws Exception {
-        return execute(new Task<Instance>() {
-            public Instance call(State state) throws IOException {
-                if (state.instances.get(name) != null) {
-                    throw new IllegalArgumentException("Instance '" + name + "' already exists");
-                }
-                if (!settings.getProfiles().isEmpty()) {
-                    try {
-                        ProfileApplier.verify();
-                    } catch (NoClassDefFoundError error) {
-                        throw new IllegalArgumentException("Profile service package is not available");
-                    }
+        return execute(state -> {
+            if (state.instances.get(name) != null) {
+                throw new IllegalArgumentException("Instance '" + name + "' already exists");
+            }
+            if (!settings.getProfiles().isEmpty()) {
+                try {
+                    ProfileApplier.verify();
+                } catch (NoClassDefFoundError error) {
+                    throw new IllegalArgumentException("Profile service package is not available");
                 }
+            }
 
-                String loc = settings.getLocation() != null ? settings.getLocation() : name;
-                File karafBase = new File(loc);
-                if (!karafBase.isAbsolute()) {
-                    karafBase = new File(storageLocation, loc);
-                }
-                int sshPort = settings.getSshPort();
-                if (sshPort <= 0) {
-                    sshPort = ++state.defaultSshPortStart;
-                }
-                String sshHost = settings.getAddress();
-                int rmiRegistryPort = settings.getRmiRegistryPort();
-                if (rmiRegistryPort <= 0) {
-                    rmiRegistryPort = ++state.defaultRmiRegistryPortStart;
-                }
-                int rmiServerPort = settings.getRmiServerPort();
-                if (rmiServerPort <= 0) {
-                    rmiServerPort = ++state.defaultRmiServerPortStart;
-                }
-                logInfo("Creating new instance on SSH port %d and registry port %d / RMI server port %d at: %s",
-                        printOutput, sshPort, rmiRegistryPort, rmiServerPort, karafBase);
-
-                mkdir(karafBase, "bin", printOutput);
-                mkdir(karafBase, "etc", printOutput);
-                mkdir(karafBase, "system", printOutput);
-                mkdir(karafBase, "deploy", printOutput);
-                mkdir(karafBase, "data", printOutput);
-
-                Map<String, URL> textResources = new HashMap<String, URL>(settings.getTextResources());
-                Map<String, URL> binaryResources = new HashMap<String, URL>(settings.getBinaryResources());
-
-                String[] resources = 
-                {
-                    "etc/all.policy",
-                    "etc/config.properties",
-                    "etc/custom.properties",
-                    "etc/distribution.info",
-                    "etc/equinox-debug.properties",
-                    "etc/java.util.logging.properties",
-                    "etc/jmx.acl.cfg",
-                    "etc/jre.properties",
-                    "etc/keys.properties",
-                    "etc/org.apache.felix.fileinstall-deploy.cfg",
-                    "etc/org.apache.karaf.features.repos.cfg",
-                    "etc/org.apache.karaf.jaas.cfg",
-                    "etc/org.apache.karaf.kar.cfg",
-                    "etc/org.apache.karaf.log.cfg",
-                    "etc/org.ops4j.pax.logging.cfg",
-                    "etc/org.ops4j.pax.url.mvn.cfg",
-                    "etc/shell.init.script",
-                    "etc/users.properties",
-                    FEATURES_CFG
-                };
-                copyResourcesToDir(resources, karafBase, textResources, printOutput);
-                addFeaturesFromSettings(new File(karafBase, FEATURES_CFG), settings);
-
-                // The startup.properties is now generated by the karaf maven plugin, so
-                // we use the one from the root instance instead of embedding it
-                File rootEtc = new File(System.getProperty("karaf.etc"));
-                copy(new File(rootEtc, "startup.properties"), new File(karafBase, "etc/startup.properties"));
-
-                // align child with any bundles we have overriden in the root instance
-                File rootOverrides = new File(rootEtc, "overrides.properties");
-                if (rootOverrides.exists()) {
-                    copy(rootOverrides, new File(karafBase, "etc/overrides.properties"));
-                }
-                
-                HashMap<String, String> props = new HashMap<String, String>();
-                props.put("${SUBST-KARAF-NAME}", name);
-                props.put("${SUBST-KARAF-HOME}", System.getProperty("karaf.home"));
-                props.put("${SUBST-KARAF-BASE}", karafBase.getPath());
-                props.put("${SUBST-SSH-PORT}", Integer.toString(sshPort));
-                props.put("${SUBST-SSH-HOST}", sshHost);
-                props.put("${SUBST-RMI-REGISTRY-PORT}", Integer.toString(rmiRegistryPort));
-                props.put("${SUBST-RMI-SERVER-PORT}", Integer.toString(rmiServerPort));
-
-                String[] filteredResources =
-                {
-                     "etc/system.properties",
-                     "etc/org.apache.karaf.shell.cfg",
-                     "etc/org.apache.karaf.management.cfg",
-                     "bin/karaf",
-                     "bin/start",
-                     "bin/stop",
-                     "bin/karaf.bat",
-                     "bin/start.bat",
-                     "bin/stop.bat"
-                };
-                copyFilteredResourcesToDir(filteredResources, karafBase, textResources, props, printOutput);
+            String loc = settings.getLocation() != null ? settings.getLocation() : name;
+            File karafBase = new File(loc);
+            if (!karafBase.isAbsolute()) {
+                karafBase = new File(storageLocation, loc);
+            }
+            int sshPort = settings.getSshPort();
+            if (sshPort <= 0) {
+                sshPort = ++state.defaultSshPortStart;
+            }
+            String sshHost = settings.getAddress();
+            int rmiRegistryPort = settings.getRmiRegistryPort();
+            if (rmiRegistryPort <= 0) {
+                rmiRegistryPort = ++state.defaultRmiRegistryPortStart;
+            }
+            int rmiServerPort = settings.getRmiServerPort();
+            if (rmiServerPort <= 0) {
+                rmiServerPort = ++state.defaultRmiServerPortStart;
+            }
+            logInfo("Creating new instance on SSH port %d and registry port %d / RMI server port %d at: %s",
+                    printOutput, sshPort, rmiRegistryPort, rmiServerPort, karafBase);
+
+            mkdir(karafBase, "bin", printOutput);
+            mkdir(karafBase, "etc", printOutput);
+            mkdir(karafBase, "system", printOutput);
+            mkdir(karafBase, "deploy", printOutput);
+            mkdir(karafBase, "data", printOutput);
+
+            Map<String, URL> textResources = new HashMap<>(settings.getTextResources());
+            Map<String, URL> binaryResources = new HashMap<>(settings.getBinaryResources());
+
+            String[] resources =
+            {
+                "etc/all.policy",
+                "etc/config.properties",
+                "etc/custom.properties",
+                "etc/distribution.info",
+                "etc/equinox-debug.properties",
+                "etc/java.util.logging.properties",
+                "etc/jmx.acl.cfg",
+                "etc/jre.properties",
+                "etc/keys.properties",
+                "etc/org.apache.felix.fileinstall-deploy.cfg",
+                "etc/org.apache.karaf.features.repos.cfg",
+                "etc/org.apache.karaf.jaas.cfg",
+                "etc/org.apache.karaf.kar.cfg",
+                "etc/org.apache.karaf.log.cfg",
+                "etc/org.ops4j.pax.logging.cfg",
+                "etc/org.ops4j.pax.url.mvn.cfg",
+                "etc/shell.init.script",
+                "etc/users.properties",
+                FEATURES_CFG
+            };
+            copyResourcesToDir(resources, karafBase, textResources, printOutput);
+            addFeaturesFromSettings(new File(karafBase, FEATURES_CFG), settings);
+
+            // The startup.properties is now generated by the karaf maven plugin, so
+            // we use the one from the root instance instead of embedding it
+            File rootEtc = new File(System.getProperty("karaf.etc"));
+            copy(new File(rootEtc, "startup.properties"), new File(karafBase, "etc/startup.properties"));
+
+            // align child with any bundles we have overriden in the root instance
+            File rootOverrides = new File(rootEtc, "overrides.properties");
+            if (rootOverrides.exists()) {
+                copy(rootOverrides, new File(karafBase, "etc/overrides.properties"));
+            }
 
-                try {
-                    chmod(new File(karafBase, "bin/karaf"), "a+x");
-                    chmod(new File(karafBase, "bin/start"), "a+x");
-                    chmod(new File(karafBase, "bin/stop"), "a+x");
-                } catch (IOException e) {
-                    LOGGER.debug("Could not set file mode on scripts.", e);
-                }
+            HashMap<String, String> props = new HashMap<>();
+            props.put("${SUBST-KARAF-NAME}", name);
+            props.put("${SUBST-KARAF-HOME}", System.getProperty("karaf.home"));
+            props.put("${SUBST-KARAF-BASE}", karafBase.getPath());
+            props.put("${SUBST-SSH-PORT}", Integer.toString(sshPort));
+            props.put("${SUBST-SSH-HOST}", sshHost);
+            props.put("${SUBST-RMI-REGISTRY-PORT}", Integer.toString(rmiRegistryPort));
+            props.put("${SUBST-RMI-SERVER-PORT}", Integer.toString(rmiServerPort));
+
+            String[] filteredResources =
+            {
+                 "etc/system.properties",
+                 "etc/org.apache.karaf.shell.cfg",
+                 "etc/org.apache.karaf.management.cfg",
+                 "bin/karaf",
+                 "bin/start",
+                 "bin/stop",
+                 "bin/karaf.bat",
+                 "bin/start.bat",
+                 "bin/stop.bat"
+            };
+            copyFilteredResourcesToDir(filteredResources, karafBase, textResources, props, printOutput);
 
-                for (String resource : textResources.keySet()) {
-                    copyFilteredResourceToDir(resource, karafBase, textResources, props, printOutput);
-                }
+            try {
+                chmod(new File(karafBase, "bin/karaf"), "a+x");
+                chmod(new File(karafBase, "bin/start"), "a+x");
+                chmod(new File(karafBase, "bin/stop"), "a+x");
+            } catch (IOException e) {
+                LOGGER.debug("Could not set file mode on scripts.", e);
+            }
 
-                for (String resource : binaryResources.keySet()) {
-                    copyBinaryResourceToDir(resource, karafBase, binaryResources, printOutput);
-                }
+            for (String resource : textResources.keySet()) {
+                copyFilteredResourceToDir(resource, karafBase, textResources, props, printOutput);
+            }
 
-                if (!settings.getProfiles().isEmpty()) {
-                    ProfileApplier.applyProfiles(karafBase, settings.getProfiles(), printOutput);
-                }
+            for (String resource : binaryResources.keySet()) {
+                copyBinaryResourceToDir(resource, karafBase, binaryResources, printOutput);
+            }
 
-                String javaOpts = settings.getJavaOpts();
-                if (javaOpts == null || javaOpts.length() == 0) {
-                    javaOpts = DEFAULT_JAVA_OPTS;
-                }
-                InstanceState is = new InstanceState();
-                is.name = name;
-                is.loc = karafBase.toString();
-                is.opts = javaOpts;
-                state.instances.put(name, is);
-                InstanceImpl instance = new InstanceImpl(InstanceServiceImpl.this, name);
-                InstanceServiceImpl.this.proxies.put(name, instance);
-                return instance;
+            if (!settings.getProfiles().isEmpty()) {
+                ProfileApplier.applyProfiles(karafBase, settings.getProfiles(), printOutput);
+            }
+
+            String javaOpts = settings.getJavaOpts();
+            if (javaOpts == null || javaOpts.length() == 0) {
+                javaOpts = DEFAULT_JAVA_OPTS;
             }
+            InstanceState is = new InstanceState();
+            is.name = name;
+            is.loc = karafBase.toString();
+            is.opts = javaOpts;
+            state.instances.put(name, is);
+            InstanceImpl instance = new InstanceImpl(InstanceServiceImpl.this, name);
+            InstanceServiceImpl.this.proxies.put(name, instance);
+            return instance;
         }, true);
     }
 
     void addFeaturesFromSettings(File featuresCfg, final InstanceSettings settings) throws IOException {
-        FileLockUtils.execute(featuresCfg, new FileLockUtils.RunnableWithProperties() {
-            public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                appendToPropList(properties, "featuresBoot", settings.getFeatures());
-                appendToPropList(properties, "featuresRepositories", settings.getFeatureURLs());
-            }
+        FileLockUtils.execute(featuresCfg, properties -> {
+            appendToPropList(properties, "featuresBoot", settings.getFeatures());
+            appendToPropList(properties, "featuresRepositories", settings.getFeatureURLs());
         }, true);
     }
 
-    private static void appendToPropList(org.apache.felix.utils.properties.Properties p, String key, List<String> elements) {
+    private static void appendToPropList(TypedProperties p, String key, List<String> elements) {
         if (elements == null) {
             return;
         }
@@ -445,36 +444,25 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     public Instance[] getInstances() {
-        return execute(new Task<Instance[]>() {
-            public Instance[] call(State state) throws IOException {
-                return proxies.values().toArray(new Instance[proxies.size()]);
-            }
-        }, false);
+        return execute(state -> proxies.values().toArray(new Instance[proxies.size()]), false);
     }
 
     public Instance getInstance(final String name) {
-        return execute(new Task<Instance>() {
-            public Instance call(State state) throws IOException {
-                return proxies.get(name);
-            }
-        }, false);
+        return execute(state -> proxies.get(name), false);
     }
 
     public void startInstance(final String name, final String javaOpts) {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                if (instance.pid != 0) {
-                    throw new IllegalStateException("Instance already started");
-                }
-                doStart(instance, name, javaOpts);
-                return null;
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
-
+            checkPid(instance);
+            if (instance.pid != 0) {
+                throw new IllegalStateException("Instance already started");
+            }
+            doStart(instance, name, javaOpts);
+            return null;
         }, true);
     }
 
@@ -532,11 +520,7 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     private StringBuilder classpathFromLibDir(File libDir) throws IOException {
-        File[] jars = libDir.listFiles(new FilenameFilter() {
-            public boolean accept(File dir, String name) {
-                return name.endsWith(".jar");
-            }
-        });
+        File[] jars = libDir.listFiles((dir, name) -> name.endsWith(".jar"));
         StringBuilder classpath = new StringBuilder();
         if (jars != null) {
             for (File jar : jars) {
@@ -574,167 +558,156 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     public void restartInstance(final String name, final String javaOpts) {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                String current = System.getProperty("karaf.name");
-                if (name.equals(current)) {
-                    String location = System.getProperty("karaf.home");
-                    StringBuilder classpath = new StringBuilder();
-                    addJar(classpath, "org.apache.karaf.instance", "org.apache.karaf.instance.core");
-                    addJar(classpath, "org.apache.karaf.shell", "org.apache.karaf.shell.core");
-                    addJar(classpath, "org.ops4j.pax.logging", "pax-logging-api");
-                    addJar(classpath, "jline", "jline");
-                    String command = "\""
-                            + new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath()
-                            + "\" "
-                            + " -Djava.util.logging.config.file=\"" + new File(location, "etc/java.util.logging.properties").getCanonicalPath() + "\""
-                            + " -Dkaraf.home=\"" + System.getProperty("karaf.home") + "\""
-                            + " -Dkaraf.base=\"" + new File(location).getCanonicalPath() + "\""
-                            + " -Dkaraf.data=\"" + new File(new File(location).getCanonicalPath(), "data") + "\""
-                            + " -Dkaraf.etc=\"" + new File(new File(location).getCanonicalPath(), "etc") + "\""
-                            + " -Dkaraf.instances=\"" + System.getProperty("karaf.instances") + "\""
-                            + " -classpath \"" + classpath.toString() + "\""
-                            + " " + Execute.class.getName()
-                            + " restart --java-opts \"" + javaOpts + "\" " + name;
-                    new ProcessBuilderFactoryImpl().newBuilder()
-                            .directory(new File(System.getProperty("karaf.home")))
-                            .command(command)
-                            .start();
-                } else {
-                    checkPid(instance);
-                    if (instance.pid != 0) {
-                        cleanShutdown(instance);
-                    }
-                    doStart(instance, name, javaOpts);
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
+            String current = System.getProperty("karaf.name");
+            if (name.equals(current)) {
+                String location = System.getProperty("karaf.home");
+                StringBuilder classpath = new StringBuilder();
+                addJar(classpath, "org.apache.karaf.instance", "org.apache.karaf.instance.core");
+                addJar(classpath, "org.apache.karaf.shell", "org.apache.karaf.shell.core");
+                addJar(classpath, "org.ops4j.pax.logging", "pax-logging-api");
+                addJar(classpath, "jline", "jline");
+                String command = "\""
+                        + new File(System.getProperty("java.home"), ScriptUtils.isWindows() ? "bin\\java.exe" : "bin/java").getCanonicalPath()
+                        + "\" "
+                        + " -Djava.util.logging.config.file=\"" + new File(location, "etc/java.util.logging.properties").getCanonicalPath() + "\""
+                        + " -Dkaraf.home=\"" + System.getProperty("karaf.home") + "\""
+                        + " -Dkaraf.base=\"" + new File(location).getCanonicalPath() + "\""
+                        + " -Dkaraf.data=\"" + new File(new File(location).getCanonicalPath(), "data") + "\""
+                        + " -Dkaraf.etc=\"" + new File(new File(location).getCanonicalPath(), "etc") + "\""
+                        + " -Dkaraf.instances=\"" + System.getProperty("karaf.instances") + "\""
+                        + " -classpath \"" + classpath.toString() + "\""
+                        + " " + Execute.class.getName()
+                        + " restart --java-opts \"" + javaOpts + "\" " + name;
+                new ProcessBuilderFactoryImpl().newBuilder()
+                        .directory(new File(System.getProperty("karaf.home")))
+                        .command(command)
+                        .start();
+            } else {
+                checkPid(instance);
+                if (instance.pid != 0) {
+                    cleanShutdown(instance);
                 }
-                return null;
+                doStart(instance, name, javaOpts);
             }
+            return null;
         }, true);
     }
 
     public void stopInstance(final String name) {
-        
-        Integer pid = (Integer)execute(new Task<Object>() {
-            
-            public Object call(State state) throws IOException {
-                int rootInstancePID = 0;
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                if (instance.pid == 0) {
-                    throw new IllegalStateException("Instance already stopped");
-                }
-                cleanShutdown(instance);
-                if (instance.pid > 0) {
-                    if (!instance.root) {
-                        Process process = new ProcessBuilderFactoryImpl().newBuilder().attach(instance.pid);
-                        process.destroy();
-                    } else {
-                        //can't simply destroy root instance here
-                        //as it will lose the update in instances.properties
-                        //because of no chance to run the saveData
-                        rootInstancePID = instance.pid;
-                    }
-                    instance.pid = 0;
-                    
+        Integer pid = execute(state -> {
+            int rootInstancePID = 0;
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
+            checkPid(instance);
+            if (instance.pid == 0) {
+                throw new IllegalStateException("Instance already stopped");
+            }
+            cleanShutdown(instance);
+            if (instance.pid > 0) {
+                if (!instance.root) {
+                    Process process = new ProcessBuilderFactoryImpl().newBuilder().attach(instance.pid);
+                    process.destroy();
+                } else {
+                    //can't simply destroy root instance here
+                    //as it will lose the update in instances.properties
+                    //because of no chance to run the saveData
+                    rootInstancePID = instance.pid;
                 }
-                return rootInstancePID;
+                instance.pid = 0;
+
             }
+            return rootInstancePID;
         }, true);
-        if (pid.intValue() != 0 && isInstanceRoot(name)) {
+        if (pid != 0 && isInstanceRoot(name)) {
             Process process;
             try {
-                process = new ProcessBuilderFactoryImpl().newBuilder().attach(pid.intValue());
+                process = new ProcessBuilderFactoryImpl().newBuilder().attach(pid);
                 process.destroy(); 
             } catch (IOException e) {
                 LOGGER.debug("Unable to cleanly shutdown root instance ", e);
             }
-                     
         }
     }
 
     public void destroyInstance(final String name) {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                if (instance.pid != 0) {
-                    throw new IllegalStateException("Instance not stopped");
-                }
-                deleteFile(new File(instance.loc));
-                state.instances.remove(name);
-                InstanceServiceImpl.this.proxies.remove(name);
-                return null;
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
+            checkPid(instance);
+            if (instance.pid != 0) {
+                throw new IllegalStateException("Instance not stopped");
             }
+            deleteFile(new File(instance.loc));
+            state.instances.remove(name);
+            InstanceServiceImpl.this.proxies.remove(name);
+            return null;
         }, true);
     }
 
     public void renameInstance(final String oldName, final String newName, final boolean printOutput) throws Exception {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                if (state.instances.get(newName) != null) {
-                    throw new IllegalArgumentException("Instance " + newName + " already exists");
-                }
-                InstanceState instance = state.instances.get(oldName);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + oldName + " not found");
-                }
-                if (instance.root) {
-                    throw new IllegalArgumentException("Root instance cannot be renamed");
-                }
-                checkPid(instance);
-                if (instance.pid != 0) {
-                    throw new IllegalStateException("Instance not stopped");
-                }
+        execute(state -> {
+            if (state.instances.get(newName) != null) {
+                throw new IllegalArgumentException("Instance " + newName + " already exists");
+            }
+            InstanceState instance = state.instances.get(oldName);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + oldName + " not found");
+            }
+            if (instance.root) {
+                throw new IllegalArgumentException("Root instance cannot be renamed");
+            }
+            checkPid(instance);
+            if (instance.pid != 0) {
+                throw new IllegalStateException("Instance not stopped");
+            }
 
-                println("Renaming instance "
-                        + SimpleAnsi.INTENSITY_BOLD + oldName + SimpleAnsi.INTENSITY_NORMAL
-                        + " to "
-                        + SimpleAnsi.INTENSITY_BOLD + newName + SimpleAnsi.INTENSITY_NORMAL);
-                // rename directory
-                String oldLocationPath = instance.loc;
-                File oldLocation = new File(oldLocationPath);
-                String basedir = oldLocation.getParent();
-                File newLocation = new File(basedir, newName);
-                oldLocation.renameTo(newLocation);
-                // create the properties map including the instance name and instance location
-                // TODO: replacing is bad, we should re-extract the needed files
-                HashMap<String, String> props = new HashMap<String, String>();
-                props.put(oldName, newName);
-                props.put(oldLocationPath, newLocation.getPath());
-                // replace all references to the "old" name by the new one in etc/system.properties
-                // NB: it's replacement to avoid to override the user's changes
-                filterResource(newLocation, "etc/system.properties", props);
-                // replace all references to the "old" name by the new one in bin/karaf
-                filterResource(newLocation, "bin/karaf", props);
-                filterResource(newLocation, "bin/start", props);
-                filterResource(newLocation, "bin/stop", props);
-                filterResource(newLocation, "bin/karaf.bat", props);
-                filterResource(newLocation, "bin/start.bat", props);
-                filterResource(newLocation, "bin/stop.bat", props);
-                // update instance
-                instance.name = newName;
-                instance.loc = newLocation.getPath();
-                state.instances.put(newName, instance);
-                state.instances.remove(oldName);
-                InstanceImpl proxy = InstanceServiceImpl.this.proxies.remove(oldName);
-                if (proxy == null) {
-                    proxy = new InstanceImpl(InstanceServiceImpl.this, newName);
-                } else {
-                    proxy.doSetName(newName);
-                }
-                InstanceServiceImpl.this.proxies.put(newName, proxy);
-                return null;
+            println("Renaming instance "
+                    + SimpleAnsi.INTENSITY_BOLD + oldName + SimpleAnsi.INTENSITY_NORMAL
+                    + " to "
+                    + SimpleAnsi.INTENSITY_BOLD + newName + SimpleAnsi.INTENSITY_NORMAL);
+            // rename directory
+            String oldLocationPath = instance.loc;
+            File oldLocation = new File(oldLocationPath);
+            String basedir = oldLocation.getParent();
+            File newLocation = new File(basedir, newName);
+            oldLocation.renameTo(newLocation);
+            // create the properties map including the instance name and instance location
+            // TODO: replacing is bad, we should re-extract the needed files
+            HashMap<String, String> props = new HashMap<String, String>();
+            props.put(oldName, newName);
+            props.put(oldLocationPath, newLocation.getPath());
+            // replace all references to the "old" name by the new one in etc/system.properties
+            // NB: it's replacement to avoid to override the user's changes
+            filterResource(newLocation, "etc/system.properties", props);
+            // replace all references to the "old" name by the new one in bin/karaf
+            filterResource(newLocation, "bin/karaf", props);
+            filterResource(newLocation, "bin/start", props);
+            filterResource(newLocation, "bin/stop", props);
+            filterResource(newLocation, "bin/karaf.bat", props);
+            filterResource(newLocation, "bin/start.bat", props);
+            filterResource(newLocation, "bin/stop.bat", props);
+            // update instance
+            instance.name = newName;
+            instance.loc = newLocation.getPath();
+            state.instances.put(newName, instance);
+            state.instances.remove(oldName);
+            InstanceImpl proxy = InstanceServiceImpl.this.proxies.remove(oldName);
+            if (proxy == null) {
+                proxy = new InstanceImpl(InstanceServiceImpl.this, newName);
+            } else {
+                proxy.doSetName(newName);
             }
+            InstanceServiceImpl.this.proxies.put(newName, proxy);
+            return null;
         }, true);
     }
 
@@ -742,65 +715,62 @@ public class InstanceServiceImpl implements InstanceService {
         final int instanceSshPort = getInstanceSshPort(name);
         final int instanceRmiRegistryPort = getInstanceRmiRegistryPort(name);
         final int instanceRmiServerPort = getInstanceRmiServerPort(name);
+        return execute(state -> {
+            if (state.instances.get(cloneName) != null) {
+                throw new IllegalArgumentException("Instance " + cloneName + " already exists");
+            }
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
 
-        return execute(new Task<Instance>() {
-            public Instance call(State state) throws IOException {
-                if (state.instances.get(cloneName) != null) {
-                    throw new IllegalArgumentException("Instance " + cloneName + " already exists");
-                }
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-
-                // define the clone instance location
-                String cloneLocationPath = settings.getLocation() != null ? settings.getLocation() : cloneName;
-                File cloneLocation = new File(cloneLocationPath);
-                if (!cloneLocation.isAbsolute()) {
-                    cloneLocation = new File(storageLocation, cloneLocationPath);
-                }
-                // copy instance directory
-                String locationPath = instance.loc;
-                File location = new File(locationPath);
-                copy(location, cloneLocation);
-                // create the properties map including the instance name, location, ssh and rmi port numbers
-                // TODO: replacing stuff anywhere is not really good, we might end up replacing unwanted stuff
-                // TODO: if no ports are overriden, shouldn't we choose new ports ?
-                HashMap<String, String> props = new HashMap<String, String>();
-                props.put(name, cloneName);
-                props.put(locationPath, cloneLocationPath);
-                if (settings.getSshPort() > 0)
-                    props.put(Integer.toString(instanceSshPort), Integer.toString(settings.getSshPort()));
-                if (settings.getRmiRegistryPort() > 0)
-                    props.put(Integer.toString(instanceRmiRegistryPort), Integer.toString(settings.getRmiRegistryPort()));
-                if (settings.getRmiServerPort() > 0)
-                    props.put(Integer.toString(instanceRmiServerPort), Integer.toString(settings.getRmiServerPort()));
-
-                // filtering clone files
-                filterResource(cloneLocation, "etc/custom.properties", props);
-                filterResource(cloneLocation, "etc/org.apache.karaf.management.cfg", props);
-                filterResource(cloneLocation, "etc/org.apache.karaf.shell.cfg", props);
-                filterResource(cloneLocation, "etc/system.properties", props);
-                filterResource(cloneLocation, "bin/karaf", props);
-                filterResource(cloneLocation, "bin/start", props);
-                filterResource(cloneLocation, "bin/stop", props);
-                filterResource(cloneLocation, "bin/karaf.bat", props);
-                filterResource(cloneLocation, "bin/start.bat", props);
-                filterResource(cloneLocation, "bin/stop.bat", props);
-                // create and add the clone instance in the registry
-                String javaOpts = settings.getJavaOpts();
-                if (javaOpts == null || javaOpts.length() == 0) {
-                    javaOpts = DEFAULT_JAVA_OPTS;
-                }
-                InstanceState is = new InstanceState();
-                is.name = cloneName;
-                is.loc = cloneLocation.toString();
-                is.opts = javaOpts;
-                state.instances.put(cloneName, is);
-                InstanceImpl cloneInstance = new InstanceImpl(InstanceServiceImpl.this, cloneName);
-                InstanceServiceImpl.this.proxies.put(cloneName, cloneInstance);
-                return cloneInstance;
+            // define the clone instance location
+            String cloneLocationPath = settings.getLocation() != null ? settings.getLocation() : cloneName;
+            File cloneLocation = new File(cloneLocationPath);
+            if (!cloneLocation.isAbsolute()) {
+                cloneLocation = new File(storageLocation, cloneLocationPath);
+            }
+            // copy instance directory
+            String locationPath = instance.loc;
+            File location = new File(locationPath);
+            copy(location, cloneLocation);
+            // create the properties map including the instance name, location, ssh and rmi port numbers
+            // TODO: replacing stuff anywhere is not really good, we might end up replacing unwanted stuff
+            // TODO: if no ports are overriden, shouldn't we choose new ports ?
+            HashMap<String, String> props = new HashMap<String, String>();
+            props.put(name, cloneName);
+            props.put(locationPath, cloneLocationPath);
+            if (settings.getSshPort() > 0)
+                props.put(Integer.toString(instanceSshPort), Integer.toString(settings.getSshPort()));
+            if (settings.getRmiRegistryPort() > 0)
+                props.put(Integer.toString(instanceRmiRegistryPort), Integer.toString(settings.getRmiRegistryPort()));
+            if (settings.getRmiServerPort() > 0)
+                props.put(Integer.toString(instanceRmiServerPort), Integer.toString(settings.getRmiServerPort()));
+
+            // filtering clone files
+            filterResource(cloneLocation, "etc/custom.properties", props);
+            filterResource(cloneLocation, "etc/org.apache.karaf.management.cfg", props);
+            filterResource(cloneLocation, "etc/org.apache.karaf.shell.cfg", props);
+            filterResource(cloneLocation, "etc/system.properties", props);
+            filterResource(cloneLocation, "bin/karaf", props);
+            filterResource(cloneLocation, "bin/start", props);
+            filterResource(cloneLocation, "bin/stop", props);
+            filterResource(cloneLocation, "bin/karaf.bat", props);
+            filterResource(cloneLocation, "bin/start.bat", props);
+            filterResource(cloneLocation, "bin/stop.bat", props);
+            // create and add the clone instance in the registry
+            String javaOpts = settings.getJavaOpts();
+            if (javaOpts == null || javaOpts.length() == 0) {
+                javaOpts = DEFAULT_JAVA_OPTS;
             }
+            InstanceState is = new InstanceState();
+            is.name = cloneName;
+            is.loc = cloneLocation.toString();
+            is.opts = javaOpts;
+            state.instances.put(cloneName, is);
+            InstanceImpl cloneInstance = new InstanceImpl(InstanceServiceImpl.this, cloneName);
+            InstanceServiceImpl.this.proxies.put(cloneName, cloneInstance);
+            return cloneInstance;
         }, true);
     }
 
@@ -816,17 +786,12 @@ public class InstanceServiceImpl implements InstanceService {
     protected void cleanShutdown(InstanceState instance) {
         try {
             File file = new File(new File(instance.loc, "etc"), CONFIG_PROPERTIES_FILE_NAME);
-            URL configPropURL = file.toURI().toURL();
-            Properties props = loadPropertiesFile(configPropURL);
+            Properties props = PropertiesLoader.loadPropertiesFile(file.toURI().toURL(), false);
             props.put("karaf.base", new File(instance.loc).getCanonicalPath());
             props.put("karaf.home", System.getProperty("karaf.home"));
             props.put("karaf.data", new File(new File(instance.loc), "data").getCanonicalPath());
             props.put("karaf.etc", new File(new File(instance.loc), "etc").getCanonicalPath());
-            for (Enumeration<?> e = props.propertyNames(); e.hasMoreElements();) {
-                String key = (String) e.nextElement();
-                props.setProperty(key,
-                        substVars(props.getProperty(key), key, null, props));
-            }
+            InterpolationHelper.performSubstitution(props, null, true, false, true);
             int port = Integer.parseInt(props.getProperty(KARAF_SHUTDOWN_PORT, "0"));
             String host = props.getProperty(KARAF_SHUTDOWN_HOST, "localhost");
             String portFile = props.getProperty(KARAF_SHUTDOWN_PORT_FILE);
@@ -892,11 +857,7 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     private int getKarafPort(final String name, final String path, final String key) {
-        return execute(new Task<Integer>() {
-            public Integer call(State state) throws IOException {
-                return InstanceServiceImpl.this.getKarafPort(state, name, path, key);
-            }
-        }, false);
+        return execute(state -> getKarafPort(state, name, path, key), false);
     }
 
 
@@ -907,10 +868,9 @@ public class InstanceServiceImpl implements InstanceService {
         }
         File f = new File(instance.loc, path);
         try {
-            return FileLockUtils.execute(f, new FileLockUtils.CallableWithProperties<Integer>() {
-                public Integer call(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                    return Integer.parseInt(properties.get(key).toString());
-                }
+            return FileLockUtils.execute(f, properties -> {
+                Object obj = properties.get(key);
+                return obj instanceof Number ? ((Number) obj).intValue() : Integer.parseInt(obj.toString());
             }, false);
         } catch (IOException e) {
             return 0;
@@ -918,33 +878,25 @@ public class InstanceServiceImpl implements InstanceService {
     }
 
     private void setKarafPort(final String name, final String path, final String key, final int port) throws IOException {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                if (instance.pid != 0) {
-                    throw new IllegalStateException("Instance is not stopped");
-                }
-                File f = new File(instance.loc, path);
-                FileLockUtils.execute(f, new FileLockUtils.RunnableWithProperties() {
-                    public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                        properties.put(key, Integer.toString(port));
-                    }
-                }, true);
-                return null;
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
+            checkPid(instance);
+            if (instance.pid != 0) {
+                throw new IllegalStateException("Instance is not stopped");
             }
+            File f = new File(instance.loc, path);
+            FileLockUtils.execute(f, properties -> {
+                properties.put(key, port);
+            }, true);
+            return null;
         }, true);
     }
 
     private String getKarafHost(final String name, final String path, final String key) {
-        return execute(new Task<String>() {
-            public String call(State state) throws IOException {
-                return InstanceServiceImpl.this.getKarafHost(state, name, path, key);
-            }
-        }, false);
+        return execute(state -> InstanceServiceImpl.this.getKarafHost(state, name, path, key), false);
     }
 
     private String getKarafHost(State state, String name, String path, final String key) {
@@ -954,108 +906,92 @@ public class InstanceServiceImpl implements InstanceService {
         }
         File f = new File(instance.loc, path);
         try {
-            return FileLockUtils.execute(f, new FileLockUtils.CallableWithProperties<String>() {
-                public String call(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                    return properties.get(key).toString();
-                }
-            }, false);
+            return FileLockUtils.execute(f, (TypedProperties properties) -> properties.get(key).toString(), false);
         } catch (IOException e) {
             return "0.0.0.0";
         }
     }
 
     boolean isInstanceRoot(final String name) {
-        return execute(new Task<Boolean>() {
-            public Boolean call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                return instance.root;
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            return instance.root;
         }, false);
     }
 
     String getInstanceLocation(final String name) {
-        return execute(new Task<String>() {
-            public String call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                return instance.loc;
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            return instance.loc;
         }, true);
     }
 
     int getInstancePid(final String name) {
         boolean updateInstanceProperties = isInstancePidNeedUpdate(name);
-        return execute(new Task<Integer>() {
-            public Integer call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                return instance.pid;
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            checkPid(instance);
+            return instance.pid;
         }, updateInstanceProperties);
     }
 
     String getInstanceJavaOpts(final String name) {
-        return execute(new Task<String>() {
-            public String call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                return instance.opts;
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            return instance.opts;
         }, false);
     }
 
     void changeInstanceJavaOpts(final String name, final String opts) {
-        execute(new Task<String>() {
-            public String call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                instance.opts = opts;
-                return null;
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            instance.opts = opts;
+            return null;
         }, true);
     }
 
     String getInstanceState(final String name) {
         boolean updateInstanceProperties = isInstancePidNeedUpdate(name);
-        return execute(new Task<String>() {
-            public String call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                int port = getKarafPort(state, name, "etc/org.apache.karaf.shell.cfg", "sshPort");
-                String host = getKarafHost(state, name, "etc/org.apache.karaf.shell.cfg", "sshHost");
-                if (host.equals("0.0.0.0")) {
-                    host = "localhost";
-                }
-                if (!new File(instance.loc).isDirectory() || port <= 0) {
-                    return Instance.ERROR;
-                }
-                checkPid(instance);
-                if (instance.pid == 0) {
-                    return Instance.STOPPED;
-                } else {
-                    try {
-                        Socket s = new Socket(host, port);
-                        s.close();
-                        return Instance.STARTED;
-                    } catch (Exception e) {
-                        // ignore
-                    }
-                    return Instance.STARTING;
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
+            }
+            int port = getKarafPort(state, name, "etc/org.apache.karaf.shell.cfg", "sshPort");
+            String host = getKarafHost(state, name, "etc/org.apache.karaf.shell.cfg", "sshHost");
+            if (host.equals("0.0.0.0")) {
+                host = "localhost";
+            }
+            if (!new File(instance.loc).isDirectory() || port <= 0) {
+                return Instance.ERROR;
+            }
+            checkPid(instance);
+            if (instance.pid == 0) {
+                return Instance.STOPPED;
+            } else {
+                try {
+                    Socket s = new Socket(host, port);
+                    s.close();
+                    return Instance.STARTED;
+                } catch (Exception e) {
+                    // ignore
                 }
+                return Instance.STARTING;
             }
         }, updateInstanceProperties);
     }
@@ -1285,142 +1221,36 @@ public class InstanceServiceImpl implements InstanceService {
         }
     }
 
-    private static final String DELIM_START = "${";
-    private static final String DELIM_STOP = "}";
-
-    protected static String substVars(String val, String currentKey,
-                                      Map<String, String> cycleMap, Properties configProps)
-            throws IllegalArgumentException {
-        // If there is currently no cycle map, then create
-        // one for detecting cycles for this invocation.
-        if (cycleMap == null) {
-            cycleMap = new HashMap<String, String>();
-        }
-
-        // Put the current key in the cycle map.
-        cycleMap.put(currentKey, currentKey);
-
-        // Assume we have a value that is something like:
-        // "leading ${foo.${bar}} middle ${baz} trailing"
-
-        // Find the first ending '}' variable delimiter, which
-        // will correspond to the first deepest nested variable
-        // placeholder.
-        int stopDelim = val.indexOf(DELIM_STOP);
-
-        // Find the matching starting "${" variable delimiter
-        // by looping until we find a start delimiter that is
-        // greater than the stop delimiter we have found.
-        int startDelim = val.indexOf(DELIM_START);
-        while (stopDelim >= 0) {
-            int idx = val.indexOf(DELIM_START, startDelim + DELIM_START.length());
-            if ((idx < 0) || (idx > stopDelim)) {
-                break;
-            } else if (idx < stopDelim) {
-                startDelim = idx;
-            }
-        }
-
-        // If we do not have a start or stop delimiter, then just
-        // return the existing value.
-        if ((startDelim < 0) && (stopDelim < 0)) {
-            return val;
-        }
-        // At this point, we found a stop delimiter without a start,
-        // so throw an exception.
-        else if (((startDelim < 0) || (startDelim > stopDelim))
-                && (stopDelim >= 0)) {
-            throw new IllegalArgumentException(
-                    "stop delimiter with no start delimiter: "
-                            + val);
-        }
-
-        // At this point, we have found a variable placeholder so
-        // we must perform a variable substitution on it.
-        // Using the start and stop delimiter indices, extract
-        // the first, deepest nested variable placeholder.
-        String variable =
-                val.substring(startDelim + DELIM_START.length(), stopDelim);
-
-        // Verify that this is not a recursive variable reference.
-        if (cycleMap.get(variable) != null) {
-            throw new IllegalArgumentException(
-                    "recursive variable reference: " + variable);
-        }
-
-        // Get the value of the deepest nested variable placeholder.
-        // Try to configuration properties first.
-        String substValue = (configProps != null)
-                ? configProps.getProperty(variable, null)
-                : null;
-        if (substValue == null) {
-            // Ignore unknown property values.
-            substValue = System.getProperty(variable, "");
-        }
-
-        // Remove the found variable from the cycle map, since
-        // it may appear more than once in the value and we don't
-        // want such situations to appear as a recursive reference.
-        cycleMap.remove(variable);
-
-        // Append the leading characters, the substituted value of
-        // the variable, and the trailing characters to get the new
-        // value.
-        val = val.substring(0, startDelim)
-                + substValue
-                + val.substring(stopDelim + DELIM_STOP.length(), val.length());
-
-        // Now perform substitution again, since there could still
-        // be substitutions to make.
-        val = substVars(val, currentKey, cycleMap, configProps);
-
-        // Return the value.
-        return val;
-    }
-
     public void changeInstanceSshHost(String name, String host) throws Exception {
         setKarafHost(name, "etc/org.apache.karaf.shell.cfg", "sshHost", host);      
     }
 
     private void setKarafHost(final String name, final String path, final String key, final String host) throws IOException {
-        execute(new Task<Object>() {
-            public Object call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                checkPid(instance);
-                if (instance.pid != 0) {
-                    throw new IllegalStateException("Instance is not stopped");
-                }
-                File f = new File(instance.loc, path);
-                FileLockUtils.execute(f, new FileLockUtils.RunnableWithProperties() {
-                    public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                        properties.put(key, host);
-                    }
-                }, true);
-                return null;
+        execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            checkPid(instance);
+            if (instance.pid != 0) {
+                throw new IllegalStateException("Instance is not stopped");
+            }
+            File f = new File(instance.loc, path);
+            FileLockUtils.execute(f, properties -> { properties.put(key, host); }, true);
+            return null;
         }, true);
     }
     
-    private Boolean isInstancePidNeedUpdate(final String name) {
-        return execute(new Task<Boolean>() {
-            public Boolean call(State state) throws IOException {
-                InstanceState instance = state.instances.get(name);
-                if (instance == null) {
-                    throw new IllegalArgumentException("Instance " + name + " not found");
-                }
-                int origialPid = instance.pid;
-                checkPid(instance);
-                int newPid = instance.pid;
-                if (origialPid == newPid) {
-                    return false;
-                } else {
-                    return true;
-                }
-                
+    private boolean isInstancePidNeedUpdate(final String name) {
+        return execute(state -> {
+            InstanceState instance = state.instances.get(name);
+            if (instance == null) {
+                throw new IllegalArgumentException("Instance " + name + " not found");
             }
+            int originalPid = instance.pid;
+            checkPid(instance);
+            int newPid = instance.pid;
+            return originalPid != newPid;
         }, false);
     }
 
@@ -1451,11 +1281,9 @@ public class InstanceServiceImpl implements InstanceService {
                     Files.write(configFile, config.getValue());
                 }
             }
-            FileLockUtils.execute(new File(karafBase, FEATURES_CFG), new FileLockUtils.RunnableWithProperties() {
-                public void run(org.apache.felix.utils.properties.Properties properties) throws IOException {
-                    appendToPropList(properties, "featuresBoot", effective.getFeatures());
-                    appendToPropList(properties, "featuresRepositories", effective.getRepositories());
-                }
+            FileLockUtils.execute(new File(karafBase, FEATURES_CFG), properties -> {
+                appendToPropList(properties, "featuresBoot", effective.getFeatures());
+                appendToPropList(properties, "featuresRepositories", effective.getRepositories());
             }, true);
 
             bundleContext.ungetService(reference);

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/main/src/main/java/org/apache/karaf/main/InstanceHelper.java
----------------------------------------------------------------------
diff --git a/main/src/main/java/org/apache/karaf/main/InstanceHelper.java b/main/src/main/java/org/apache/karaf/main/InstanceHelper.java
index deeb739..afff572 100644
--- a/main/src/main/java/org/apache/karaf/main/InstanceHelper.java
+++ b/main/src/main/java/org/apache/karaf/main/InstanceHelper.java
@@ -20,7 +20,6 @@ package org.apache.karaf.main;
 
 import java.io.File;
 import java.io.FileOutputStream;
-import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.RandomAccessFile;
 import java.io.Writer;
@@ -32,7 +31,7 @@ import java.nio.channels.FileLock;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.felix.utils.properties.Properties;
+import org.apache.felix.utils.properties.TypedProperties;
 import org.apache.karaf.util.locks.FileLockUtils;
 import org.osgi.framework.launch.Framework;
 
@@ -62,48 +61,44 @@ public class InstanceHelper {
                 }
                 // don't instance.properties if we're stopping and can't acquire lock
                 if (!isStartingInstance) {
-                    RandomAccessFile raf = new RandomAccessFile(propertiesFile, "rw");
                     boolean proceed = true;
-                    try {
+                    try (RandomAccessFile raf = new RandomAccessFile(propertiesFile, "rw")) {
                         FileLock lock = raf.getChannel().tryLock();
                         if (lock == null) {
                             proceed = false;
                         } else {
                             lock.release();
                         }
-                    } finally {
-                        // if proceed is true than we got the lock or OverlappingFileLockException
-                        // but we may proceed in either case
-                        raf.close();
                     }
+                    // if proceed is true than we got the lock or OverlappingFileLockException
+                    // but we may proceed in either case
+
                     if (!proceed) {
                         // we didn't acquire lock, it may mean that root container is holding the lock when
                         // stopping the child
                         return;
                     }
                 }
-                FileLockUtils.execute(propertiesFile, new FileLockUtils.RunnableWithProperties() {
-                    public void run(Properties props) throws IOException {
-                        if (props.isEmpty()) {
-                            // it's the first instance running, so we consider as root
-                            props.setProperty("count", "1");
-                            props.setProperty("item.0.name", instanceName);
-                            props.setProperty("item.0.loc", karafBase.getAbsolutePath());
-                            props.setProperty("item.0.pid", pid);
-                            props.setProperty("item.0.root", "true");
-                        } else {
-                            int count = Integer.parseInt(props.getProperty("count"));
-                            for (int i = 0; i < count; i++) {
-                                String name = props.getProperty("item." + i + ".name");
-                                if (name.equals(instanceName)) {
-                                    props.setProperty("item." + i + ".pid", pid);
-                                    return;
-                                }
+                FileLockUtils.execute(propertiesFile, (TypedProperties props) -> {
+                    if (props.isEmpty()) {
+                        // it's the first instance running, so we consider as root
+                        props.put("count", "1");
+                        props.put("item.0.name", instanceName);
+                        props.put("item.0.loc", karafBase.getAbsolutePath());
+                        props.put("item.0.pid", pid);
+                        props.put("item.0.root", "true");
+                    } else {
+                        int count = Integer.parseInt(props.get("count").toString());
+                        for (int i = 0; i < count; i++) {
+                            String name = props.get("item." + i + ".name").toString();
+                            if (name.equals(instanceName)) {
+                                props.put("item." + i + ".pid", pid);
+                                return;
                             }
-                            // it's not found, let assume it's the root instance, so 0
-                            props.setProperty("item.0.name", instanceName);
-                            props.setProperty("item.0.pid", pid);
                         }
+                        // it's not found, let assume it's the root instance, so 0
+                        props.put("item.0.name", instanceName);
+                        props.put("item.0.pid", pid);
                     }
                 }, true);
             }

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
index f7d2c7a..9c4ce12 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
@@ -138,20 +138,20 @@ public class Activator extends BaseActivator implements ManagedService {
     }
 
     protected SshServer createSshServer(SessionFactory sessionFactory) {
-        int sshPort           = getInt("sshPort", 8181);
-        String sshHost        = getString("sshHost", "0.0.0.0");
-        long sshIdleTimeout   = getLong("sshIdleTimeout", 1800000);
-        String sshRealm       = getString("sshRealm", "karaf");
-        String hostKey        = getString("hostKey", System.getProperty("karaf.etc") + "/host.key");
-        String hostKeyFormat  = getString("hostKeyFormat", "simple");
-        String authMethods    = getString("authMethods", "keyboard-interactive,password,publickey");
-        int keySize           = getInt("keySize", 4096);
-        String algorithm      = getString("algorithm", "RSA");
-        String macs           = getString("macs", "hmac-sha2-512,hmac-sha2-256,hmac-sha1");
-        String ciphers        = getString("ciphers", "aes128-ctr,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc");
-        String kexAlgorithms  = getString("kexAlgorithms", "diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1");
-        String welcomeBanner  = getString("welcomeBanner", null);
-        String moduliUrl      = getString("moduli-url", null);
+        int sshPort            = getInt("sshPort", 8181);
+        String sshHost         = getString("sshHost", "0.0.0.0");
+        long sshIdleTimeout    = getLong("sshIdleTimeout", 1800000);
+        String sshRealm        = getString("sshRealm", "karaf");
+        String hostKey         = getString("hostKey", System.getProperty("karaf.etc") + "/host.key");
+        String hostKeyFormat   = getString("hostKeyFormat", "simple");
+        String[] authMethods   = getStringArray("authMethods", "keyboard-interactive,password,publickey");
+        int keySize            = getInt("keySize", 4096);
+        String algorithm       = getString("algorithm", "RSA");
+        String[] macs          = getStringArray("macs", "hmac-sha2-512,hmac-sha2-256,hmac-sha1");
+        String[] ciphers       = getStringArray("ciphers", "aes128-ctr,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc");
+        String[] kexAlgorithms = getStringArray("kexAlgorithms", "diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1");
+        String welcomeBanner   = getString("welcomeBanner", null);
+        String moduliUrl       = getString("moduli-url", null);
 
         AbstractGeneratorHostKeyProvider keyPairProvider;
         if ("simple".equalsIgnoreCase(hostKeyFormat)) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
index 5b5330c..82c8e04 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
@@ -39,9 +39,9 @@ public class SshUtils {
     private static final Logger LOGGER = LoggerFactory.getLogger(SshUtils.class);
 
     public static <S> List<NamedFactory<S>> filter(Class<S> type,
-            Collection<NamedFactory<S>> factories, String names) {
+            Collection<NamedFactory<S>> factories, String[] names) {
         List<NamedFactory<S>> list = new ArrayList<NamedFactory<S>>();
-        for (String name : names.split(",")) {
+        for (String name : names) {
             name = name.trim();
             boolean found = false;
             for (NamedFactory<S> factory : factories) {
@@ -59,17 +59,17 @@ public class SshUtils {
         return list;
     }
 
-    public static List<NamedFactory<Mac>> buildMacs(String names) {
+    public static List<NamedFactory<Mac>> buildMacs(String[] names) {
         return filter(Mac.class, new ServerConfig().getMacFactories(), names);
     }
 
-    public static List<NamedFactory<Cipher>> buildCiphers(String names) {
+    public static List<NamedFactory<Cipher>> buildCiphers(String[] names) {
         ServerConfig defaults = new ServerConfig();
         List<NamedFactory<Cipher>> avail = defaults.getCipherFactories();
         return filter(Cipher.class, avail, names);
     }
 
-    public static List<NamedFactory<KeyExchange>> buildKexAlgorithms(String names) {
+    public static List<NamedFactory<KeyExchange>> buildKexAlgorithms(String[] names) {
         ServerConfig defaults = new ServerConfig();
         List<NamedFactory<KeyExchange>> avail = defaults.getKeyExchangeFactories();
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
index 76e61a1..994121e 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
@@ -49,11 +49,10 @@ public class UserAuthFactoriesFactory {
     private Set<String> methodSet;
     private List<NamedFactory<UserAuth>> factories;
 
-   public void setAuthMethods(String methods) {
+   public void setAuthMethods(String[] methods) {
         this.methodSet = new HashSet<String>();
         this.factories = new ArrayList<NamedFactory<UserAuth>>();
-        String[] ams = methods.split(",");
-        for (String am : ams) {
+        for (String am : methods) {
             if (PASSWORD_METHOD.equals(am)) {
                 this.factories.add(new UserAuthPasswordFactory());
             } else if (KEYBOARD_INTERACTIVE_METHOD.equals(am)) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
----------------------------------------------------------------------
diff --git a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
index 5ddf77c..eda708c 100644
--- a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
+++ b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
@@ -36,7 +36,7 @@ public class SshUtilsTest {
         // verify our default configuration...
         String ciphers = "aes128-ctr,arcfour128,aes128-cbc,3des-cbc,blowfish-cbc";
 
-        List<NamedFactory<Cipher>> list = SshUtils.buildCiphers(ciphers);
+        List<NamedFactory<Cipher>> list = SshUtils.buildCiphers(ciphers.split(","));
 
         // verify that all configured ciphers are actually resolved...
         for (String cipher : ciphers.split(",")) {
@@ -59,7 +59,7 @@ public class SshUtilsTest {
         // verify our default configuration...
         String macs = "hmac-sha2-512,hmac-sha2-256,hmac-sha1";
 
-        List<NamedFactory<Mac>> list = SshUtils.buildMacs(macs);
+        List<NamedFactory<Mac>> list = SshUtils.buildMacs(macs.split(","));
 
         // verify that all configured HMACs are actually resolved...
         for (String mac : macs.split(",")) {
@@ -82,7 +82,7 @@ public class SshUtilsTest {
         // verify our default configuration...
         String kexAlgorithms = "diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1";
 
-        List<NamedFactory<KeyExchange>> list = SshUtils.buildKexAlgorithms(kexAlgorithms);
+        List<NamedFactory<KeyExchange>> list = SshUtils.buildKexAlgorithms(kexAlgorithms.split(","));
 
         // verify that all configured key exchange algorithms are actually resolved...
         for (String kex : kexAlgorithms.split(",")) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
----------------------------------------------------------------------
diff --git a/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java b/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
index cf9a201..31f30fa 100644
--- a/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
+++ b/util/src/main/java/org/apache/karaf/util/config/PropertiesLoader.java
@@ -19,10 +19,8 @@
 package org.apache.karaf.util.config;
 
 import java.io.File;
-import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
-import java.io.InputStream;
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.util.Enumeration;
@@ -103,9 +101,7 @@ public class PropertiesLoader {
     public static void loadSystemProperties(File file) throws IOException {
         Properties props = new Properties(false);
         try {
-            InputStream is = new FileInputStream(file);
-            props.load(is);
-            is.close();
+            props.load(file);
         } catch (Exception e1) {
             // Ignore
         }
@@ -146,11 +142,8 @@ public class PropertiesLoader {
 
     public static Properties loadPropertiesFile(URL configPropURL, boolean failIfNotFound) throws Exception {
         Properties configProps = new Properties(null, false);
-        InputStream is = null;
         try {
-            is = configPropURL.openConnection().getInputStream();
-            configProps.load(is);
-            is.close();
+            configProps.load(configPropURL);
         } catch (FileNotFoundException ex) {
             if (failIfNotFound) {
                 throw ex;
@@ -161,15 +154,6 @@ public class PropertiesLoader {
             System.err.println("Error loading config properties from " + configPropURL);
             System.err.println("Main: " + ex);
             return configProps;
-        } finally {
-            try {
-                if (is != null) {
-                    is.close();
-                }
-            }
-            catch (IOException ex2) {
-                // Nothing we can do.
-            }
         }
         loadIncludes(INCLUDES_PROPERTY, true, configPropURL, configProps);
         loadIncludes(OPTIONALS_PROPERTY, false, configPropURL, configProps);
@@ -178,8 +162,8 @@ public class PropertiesLoader {
     }
 
     private static void loadIncludes(String propertyName, boolean mandatory, URL configPropURL, Properties configProps)
-            throws MalformedURLException, Exception {
-        String includes = (String) configProps.get(propertyName);
+            throws Exception {
+        String includes = configProps.get(propertyName);
         if (includes != null) {
             StringTokenizer st = new StringTokenizer(includes, "\" ", true);
             if (st.countTokens() > 0) {
@@ -216,24 +200,27 @@ public class PropertiesLoader {
             boolean exit = false;
             while ((st.hasMoreTokens()) && (!exit)) {
                 tok = st.nextToken(tokenList);
-                if (tok.equals("\"")) {
-                    inQuote = !inQuote;
-                    if (inQuote) {
-                        tokenList = "\"";
-                    } else {
-                        tokenList = "\" ";
-                    }
-
-                } else if (tok.equals(" ")) {
-                    if (tokStarted) {
-                        retVal = tokBuf.toString();
-                        tokStarted = false;
-                        tokBuf = new StringBuffer(10);
-                        exit = true;
-                    }
-                } else {
-                    tokStarted = true;
-                    tokBuf.append(tok.trim());
+                switch (tok) {
+                    case "\"":
+                        inQuote = !inQuote;
+                        if (inQuote) {
+                            tokenList = "\"";
+                        } else {
+                            tokenList = "\" ";
+                        }
+                        break;
+                    case " ":
+                        if (tokStarted) {
+                            retVal = tokBuf.toString();
+                            tokStarted = false;
+                            tokBuf = new StringBuffer(10);
+                            exit = true;
+                        }
+                        break;
+                    default:
+                        tokStarted = true;
+                        tokBuf.append(tok.trim());
+                        break;
                 }
             }
 


[4/4] karaf git commit: [KARAF-5074] Better support for typed configs for built-in features and bin/client

Posted by gn...@apache.org.
[KARAF-5074] Better support for typed configs for built-in features and bin/client

Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/02dede35
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/02dede35
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/02dede35

Branch: refs/heads/master
Commit: 02dede354f5322dc8b8992b471603bd6d1f444ca
Parents: b3572bf
Author: Guillaume Nodet <gn...@apache.org>
Authored: Mon Apr 10 16:30:22 2017 +0200
Committer: Guillaume Nodet <gn...@apache.org>
Committed: Tue Apr 11 12:17:52 2017 +0200

----------------------------------------------------------------------
 .../standard/src/main/feature/feature.xml       |   14 +-
 client/pom.xml                                  |   12 +
 .../org/apache/karaf/client/ClientConfig.java   |  120 +-
 .../apache/karaf/client/ClientConfigTest.java   |    2 +-
 .../karaf/features/internal/osgi/Activator.java |    4 +-
 .../internal/service/BootFeaturesInstaller.java |    6 +-
 .../service/FeatureConfigInstaller.java         |    2 +-
 .../service/BootFeaturesInstallerTest.java      |    8 +-
 instance/pom.xml                                |    1 +
 .../core/internal/InstanceServiceImpl.java      | 1072 ++++++++----------
 .../org/apache/karaf/main/InstanceHelper.java   |   51 +-
 .../org/apache/karaf/shell/ssh/Activator.java   |   28 +-
 .../org/apache/karaf/shell/ssh/SshUtils.java    |   10 +-
 .../shell/ssh/UserAuthFactoriesFactory.java     |    5 +-
 .../apache/karaf/shell/ssh/SshUtilsTest.java    |    6 +-
 .../karaf/util/config/PropertiesLoader.java     |   63 +-
 .../apache/karaf/util/locks/FileLockUtils.java  |   90 +-
 .../karaf/util/tracker/BaseActivator.java       |   24 +
 18 files changed, 658 insertions(+), 860 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/assemblies/features/standard/src/main/feature/feature.xml
----------------------------------------------------------------------
diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml
index 95ed262..0993c27 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -199,7 +199,7 @@
             # 3: DEBUG
             # 4: TRACE
             #
-            #logLevel=1
+            #logLevel = 1
 
             #
             # Specify an additional welcome banner to be displayed when a user logs into the server.
@@ -217,6 +217,7 @@
             # This property define the default value when you use the Karaf shell console.
             # You can change the completion mode directly in the shell console, using shell:completion command.
             #
+            completionMode = GLOBAL
 
             #
             # Override allowed SSH cipher algorithms.
@@ -242,7 +243,6 @@
             #
             # moduli-url = external moduli-url users wanna use
 
-            completionMode = GLOBAL
         </config>
         <bundle dependency="true" start-level="30">mvn:org.fusesource.jansi/jansi/${jansi.version}</bundle>
         <bundle dependency="true" start-level="30">mvn:org.jline/jline/${jline.version}</bundle>
@@ -505,13 +505,13 @@
             # of lines searched for exceptions using log:exception-display. You can override this value
             # at runtime using -n in log:display.
             #
-            size = 500
+            size = I"500"
 
             #
             # The pattern used to format the log statement when using log:display. This pattern is according
             # to the log4j layout. You can override this parameter at runtime using log:display with -p.
             #
-            pattern = %d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
+            pattern = "%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n"
         </config>
         <bundle start-level="30" start="true">mvn:org.apache.karaf.log/org.apache.karaf.log.core/${project.version}</bundle>
     </feature>
@@ -626,12 +626,6 @@
     </feature>
 
     <feature name="ssh" description="Provide a SSHd server on Karaf" version="${project.version}">
-        <config name="org.apache.karaf.shell">
-            sshPort=8101
-            sshHost=0.0.0.0
-            sshRealm=karaf
-            hostKey=${karaf.etc}/host.key
-        </config>
         <feature>shell</feature>
         <feature>jaas</feature>
         <bundle start="true" start-level="30">mvn:org.apache.sshd/sshd-core/${sshd.version}</bundle>

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/client/pom.xml
----------------------------------------------------------------------
diff --git a/client/pom.xml b/client/pom.xml
index 742afba..a05a0d2 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -50,6 +50,16 @@
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.utils</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>org.apache.karaf.util</artifactId>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 
 	<build>
@@ -72,6 +82,8 @@
                         <Private-Package>
                             org.apache.karaf.client;
                             org.slf4j.impl;
+                            org.apache.felix.utils.properties;
+                            org.apache.karaf.util.config;
                             META-INF;-split-package:=merge-first
                         </Private-Package>
                         <Include-Resource>

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/client/src/main/java/org/apache/karaf/client/ClientConfig.java
----------------------------------------------------------------------
diff --git a/client/src/main/java/org/apache/karaf/client/ClientConfig.java b/client/src/main/java/org/apache/karaf/client/ClientConfig.java
index cea1241..0bfb64b 100644
--- a/client/src/main/java/org/apache/karaf/client/ClientConfig.java
+++ b/client/src/main/java/org/apache/karaf/client/ClientConfig.java
@@ -16,16 +16,14 @@
  */
 package org.apache.karaf.client;
 
+import org.apache.felix.utils.properties.Properties;
+import org.apache.felix.utils.properties.TypedProperties;
+import org.apache.karaf.util.config.PropertiesLoader;
+
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
-import java.util.Properties;
 import java.util.Set;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 public class ClientConfig {
 
@@ -46,31 +44,23 @@ public class ClientConfig {
     private String command;
     private boolean interactiveMode = false;
 
-    public ClientConfig(String[] args) throws IOException {
-        Properties shellCfg = loadProps(new File(System.getProperty("karaf.etc"), "org.apache.karaf.shell.cfg"), null);
-        Properties customCfg = loadProps(new File(System.getProperty("karaf.etc"), "custom.properties"), null);
-        
-        host = shellCfg.getProperty("sshHost", "localhost");
-        host = expandEnvVars(host);
-        String portString = shellCfg.getProperty("sshPort", "8101");
-        portString = expandEnvVars(portString);
-        
-        // if sshHost of sshPort properties contain a reference to another property (coming from 
-        // , we try to use the custom.properties value
-        if (host.contains("${")) {
-            host = replaceVariable(host, "localhost", customCfg);
-        }
+    private TypedProperties configuration;
+
+    public ClientConfig(String[] args) throws Exception {
+        File karafEtc = new File(System.getProperty("karaf.etc"));
+        PropertiesLoader.loadSystemProperties(new File(karafEtc, "system.properties"));
+        Properties configProps = PropertiesLoader.loadConfigProperties(new File(karafEtc, "config.properties"));
+        configuration = loadProps(new File(karafEtc, "org.apache.karaf.shell.cfg"), configProps);
+
+        host = getString("sshHost", "localhost");
         if (host.contains("0.0.0.0")) {
             host = "localhost";
         }
-        if (portString.contains("${")) {
-            portString = replaceVariable(portString, "8101", customCfg);
-        }
-        port = Integer.parseInt(portString);
-        level = Integer.parseInt(shellCfg.getProperty("logLevel", "0"));
+        port = getInt("sshPort", 8101);
+        level = getInt("logLevel", 0);
         retryAttempts = 0;
         retryDelay = 2;
-        idleTimeout = Long.parseLong(shellCfg.getProperty("sshIdleTimeout", "1800000"));
+        idleTimeout = getLong("sshIdleTimeout", 1800000L);
         batch = false;
         file = null;
         user = null;
@@ -171,8 +161,7 @@ public class ClientConfig {
         }
         command = commandBuilder.toString();
 
-        Map<String, String> usersCfg = new LinkedHashMap<>();
-        loadProps(new File(System.getProperty("karaf.etc") + "/users.properties"), usersCfg);
+        Map<String, String> usersCfg = PropertiesLoader.loadPropertiesFile(new File(karafEtc,"users.properties").toURI().toURL(), false);
         if (!usersCfg.isEmpty()) {
             Set<String> users = new LinkedHashSet<>();
             for (String user : usersCfg.keySet()) {
@@ -216,65 +205,50 @@ public class ClientConfig {
         System.exit(0);
     }
 
-    // tries a very basic variable substitution
-    private static String replaceVariable(String input, String defaultValue, Properties customCfg) {
+    private static TypedProperties loadProps(File file, Properties context) {
+        TypedProperties props = new TypedProperties((name, key, value) -> context.getProperty(value));
         try {
-            int indexOfDollar = input.indexOf('$');
-            int indexOfClosingBrace = input.indexOf('}', indexOfDollar + 1);
-            String varName = input.substring(indexOfDollar + 2, indexOfClosingBrace);
-            String varValue = customCfg.getProperty(varName, defaultValue);
-            return input.replace("${" + varName + "}", varValue);
+            props.load(file);
         } catch (Exception e) {
-            return input;
+            System.err.println("Warning: could not load properties from: " + file + ": " + e);
         }
+        return props;
     }
 
-    private static Properties loadProps(File file, final Map<String, String> additionalStorage) {
-        Properties props = new Properties() {
-            @Override
-            public synchronized Object put(Object key, Object value) {
-                if (additionalStorage != null) {
-                    additionalStorage.put((String) key, (String) value);
-                }
-                return super.put(key, value);
+    protected int getInt(String key, int def) {
+        if (configuration != null) {
+            Object val = configuration.get(key);
+            if (val instanceof Number) {
+                return ((Number) val).intValue();
+            } else if (val != null) {
+                return Integer.parseInt(val.toString());
             }
-        };
-        FileInputStream is = null;
-        try {
-            is = new FileInputStream(file);
-            props.load(is);
+        }
+        return def;
+    }
 
-        } catch (Exception e) {
-                System.err.println("Warning: could not load properties from: " + file + ", Reason: " + e.getMessage());
-        } finally {
-            if (is != null) {
-                try {
-                    is.close();
-                } catch (IOException e) {
-                    // ignore
-                }
+    protected long getLong(String key, long def) {
+        if (configuration != null) {
+            Object val = configuration.get(key);
+            if (val instanceof Number) {
+                return ((Number) val).longValue();
+            } else if (val != null) {
+                return Long.parseLong(val.toString());
             }
         }
-        return props;
+        return def;
     }
 
-    
-    private static String expandEnvVars(String text) {
-        Map<String, String> envMap = System.getenv();
-        String pattern = "\\$\\{([A-Za-z0-9]+)\\}";
-        Pattern expr = Pattern.compile(pattern);
-        Matcher matcher = expr.matcher(text);
-        while (matcher.find()) {
-            String envValue = envMap.get(matcher.group(1).toUpperCase());
-            if (envValue != null) {
-                envValue = envValue.replace("\\", "\\\\");
-                Pattern subexpr = Pattern.compile(Pattern.quote(matcher.group(0)));
-                text = subexpr.matcher(text).replaceAll(envValue);
+    protected String getString(String key, String def) {
+        if (configuration != null) {
+            Object val = configuration.get(key);
+            if (val != null) {
+                return val.toString();
             }
         }
-        return text;
+        return def;
     }
-    
+
     public String getHost() {
         return host;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/client/src/test/java/org/apache/karaf/client/ClientConfigTest.java
----------------------------------------------------------------------
diff --git a/client/src/test/java/org/apache/karaf/client/ClientConfigTest.java b/client/src/test/java/org/apache/karaf/client/ClientConfigTest.java
index 0732e56..7a5c7ca 100644
--- a/client/src/test/java/org/apache/karaf/client/ClientConfigTest.java
+++ b/client/src/test/java/org/apache/karaf/client/ClientConfigTest.java
@@ -26,7 +26,7 @@ import static org.junit.Assert.assertThat;
 public class ClientConfigTest {
 
     @Test
-    public void testDefaultUser() throws IOException {
+    public void testDefaultUser() throws Exception {
 
         String etc = System.getProperty("karaf.etc");
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
index ea573bd..720cbfb 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/osgi/Activator.java
@@ -146,7 +146,7 @@ public class Activator extends BaseActivator {
         register(ManagedService.class, featureFinder, props);
 
         List<Repository> repositories = new ArrayList<>();
-        String[] resourceRepositories = getString("resourceRepositories", "").split(",");
+        String[] resourceRepositories = getStringArray("resourceRepositories", "");
         long repositoryExpiration = getLong("repositoryExpiration", FeaturesService.DEFAULT_REPOSITORY_EXPIRATION);
         boolean repositoryIgnoreFailures = getBoolean("repositoryIgnoreFailures", true);
         for (String url : resourceRepositories) {
@@ -259,7 +259,7 @@ public class Activator extends BaseActivator {
         featuresServiceMBean.setFeaturesService(featuresService);
         registerMBean(featuresServiceMBean, "type=feature");
 
-        String featuresRepositories = getString("featuresRepositories", "");
+        String[] featuresRepositories = getStringArray("featuresRepositories", "");
         String featuresBoot = getString("featuresBoot", "");
         boolean featuresBootAsynchronous = getBoolean("featuresBootAsynchronous", false);
         BootFeaturesInstaller bootFeaturesInstaller = new BootFeaturesInstaller(

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
index 47eae9f..c04192c 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/BootFeaturesInstaller.java
@@ -38,7 +38,7 @@ public class BootFeaturesInstaller {
 
     private final FeaturesServiceImpl featuresService;
     private final BundleContext bundleContext;
-    private final String repositories;
+    private final String[] repositories;
     private final String features;
     private final boolean asynchronous;
     
@@ -59,7 +59,7 @@ public class BootFeaturesInstaller {
     
     public BootFeaturesInstaller(BundleContext bundleContext,
                                  FeaturesServiceImpl featuresService,
-                                 String repositories,
+                                 String[] repositories,
                                  String features,
                                  boolean asynchronous) {
         this.bundleContext = bundleContext;
@@ -90,7 +90,7 @@ public class BootFeaturesInstaller {
 
     protected void installBootFeatures() {
         try {
-            for (String repo : repositories.split(",")) {
+            for (String repo : repositories) {
                 repo = repo.trim();
                 if (!repo.isEmpty()) {
                     repo = separatorsToUnix(repo);

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
index ad96b63..c95104c 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
@@ -103,7 +103,7 @@ public class FeatureConfigInstaller {
     	for (ConfigInfo config : feature.getConfigurations()) {
             TypedProperties props = new TypedProperties();
             // trim lines
-            String val = config.getValue().replaceAll("\n\\s+", "\n");
+            String val = config.getValue();
             props.load(new StringReader(val));
 			String[] pid = parsePid(config.getName());
 			Configuration cfg = findExistingConfiguration(configAdmin, pid[0], pid[1]);

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
----------------------------------------------------------------------
diff --git a/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
index 01553d3..8f9b002 100644
--- a/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/internal/service/BootFeaturesInstallerTest.java
@@ -44,7 +44,7 @@ public class BootFeaturesInstallerTest extends TestBase {
     @Test
     @SuppressWarnings("unchecked")
     public void testParser() {
-        BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, "", "", false);
+        BootFeaturesInstaller installer = new BootFeaturesInstaller(null, null, new String[0], "", false);
         Assert.assertEquals(asList(setOf("test1", "test2"), setOf("test3")), installer.parseBootFeatures(" ( test1 , test2 ) , test3 "));
         Assert.assertEquals(asList(setOf("test1", "test2", "test3")), installer.parseBootFeatures(" test1 , test2, test3"));
         Assert.assertEquals(asList(setOf("test1"), setOf("test2"), setOf("test3")), installer.parseBootFeatures("(test1), (test2), test3"));
@@ -62,7 +62,7 @@ public class BootFeaturesInstallerTest extends TestBase {
         expectLastCall();
 
         replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, "", "config,standard,region", false);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, new String[0], "config,standard,region", false);
         bootFeatures.installBootFeatures();
         verify(impl);
 
@@ -85,7 +85,7 @@ public class BootFeaturesInstallerTest extends TestBase {
         expectLastCall();
 
         replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , "", "(transaction), ssh", false);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl , new String[0], "(transaction), ssh", false);
         bootFeatures.installBootFeatures();
         verify(impl);
     }
@@ -100,7 +100,7 @@ public class BootFeaturesInstallerTest extends TestBase {
         expectLastCall();
 
         replay(impl);
-        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, "mvn:inexistent/features/1.0/xml/features", "", false);
+        BootFeaturesInstaller bootFeatures = new BootFeaturesInstaller(null, impl, new String[] { "mvn:inexistent/features/1.0/xml/features" }, "", false);
         bootFeatures.installBootFeatures();
         verify(impl);
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/02dede35/instance/pom.xml
----------------------------------------------------------------------
diff --git a/instance/pom.xml b/instance/pom.xml
index ebfeabe..f776cfc 100644
--- a/instance/pom.xml
+++ b/instance/pom.xml
@@ -134,6 +134,7 @@
                             org.apache.karaf.instance.core.internal.osgi,
                             org.apache.felix.utils.properties;-split-package:=merge-first,
                             org.apache.karaf.util,
+                            org.apache.karaf.util.config,
                             org.apache.karaf.util.locks,
                         </Private-Package>
                     </instructions>