http://git-wip-us.apache.org/repos/asf/karaf/blob/203cfd8d/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
index f0d9ab9..943992a 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/model/Feature.java
@@ -23,6 +23,8 @@ import java.util.List;
 import java.util.Properties;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.xml.bind.annotation.XmlAccessType;
 import javax.xml.bind.annotation.XmlAccessorType;
@@ -432,6 +434,15 @@ public class Feature extends Content implements 
org.apache.karaf.features.Featur
                 c.setOwner(this);
             }
         }
+        if (config != null) {
+            for (Config c : config) {
+                String v = c.getValue();
+                v = Stream.of(v.split("\n"))
+                        .map(String::trim)
+                        .collect(Collectors.joining("\n", "", "\n"));
+                c.setValue(v);
+            }
+        }
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/karaf/blob/203cfd8d/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 7487e62..29b5eb7 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
@@ -26,8 +26,6 @@ import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
 import java.util.*;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 
 import org.apache.felix.utils.properties.InterpolationHelper;
 import 
org.apache.felix.utils.properties.InterpolationHelper.SubstitutionCallback;
@@ -102,7 +100,8 @@ public class FeatureConfigInstaller {
 
     public void installFeatureConfigs(Feature feature) throws IOException, 
InvalidSyntaxException {
        for (ConfigInfo config : feature.getConfigurations()) {
-                       Properties props = config.getProperties();
+            org.apache.felix.utils.properties.Properties props = new 
org.apache.felix.utils.properties.Properties();
+            props.load(new StringReader(config.getValue()));
                        String[] pid = parsePid(config.getName());
                        Configuration cfg = 
findExistingConfiguration(configAdmin, pid[0], pid[1]);
                        if (cfg == null) {
@@ -112,14 +111,14 @@ public class FeatureConfigInstaller {
                                cfgProps.put(CONFIG_KEY, key);
                                cfg.update(cfgProps);
                 try {
-                    updateStorage(pid[0], pid[1], props);
+                    updateStorage(pid[0], pid[1], props, false);
                 } catch (Exception e) {
                     LOGGER.warn("Can't update cfg file", e);
                 }
                        } else if (config.isAppend()) {
                 boolean update = false;
                                Dictionary<String,Object> properties = 
cfg.getProperties();
-                for (String key : props.stringPropertyNames()) {
+                for (String key : props.keySet()) {
                     if (properties.get(key) == null) {
                         properties.put(key, props.getProperty(key));
                         update = true;
@@ -128,7 +127,7 @@ public class FeatureConfigInstaller {
                 if (update) {
                     cfg.update(properties);
                     try {
-                        updateStorage(pid[0], pid[1], properties);
+                        updateStorage(pid[0], pid[1], props, true);
                     } catch (Exception e) {
                         LOGGER.warn("Can't update cfg file", e);
                     }
@@ -140,14 +139,11 @@ public class FeatureConfigInstaller {
         }
     }
 
-       private Dictionary<String, String> convertToDict(Properties props) {
+       private Dictionary<String, String> convertToDict(Map<String, String> 
props) {
                Dictionary<String, String> cfgProps = new Hashtable<String, 
String>();
-               for (@SuppressWarnings("rawtypes")
-               Enumeration e = props.propertyNames(); e.hasMoreElements();) {
-                       String key = (String) e.nextElement();
-                       String val = props.getProperty(key);
-                       cfgProps.put(key, val);
-               }
+        for (Map.Entry<String, String> e : props.entrySet()) {
+            cfgProps.put(e.getKey(), e.getValue());
+        }
                return cfgProps;
        }
 
@@ -244,7 +240,7 @@ public class FeatureConfigInstaller {
         }
     }
 
-    protected void updateStorage(String pid, String factoryPid, Dictionary 
props) throws Exception {
+    protected void updateStorage(String pid, String factoryPid, 
org.apache.felix.utils.properties.Properties props, boolean append) throws 
Exception {
         if (storage != null && configCfgStore) {
             // get the cfg file
             File cfgFile;
@@ -268,39 +264,50 @@ public class FeatureConfigInstaller {
                         cfgFile = new File(new URL((String) val).toURI());
                     }
                 } catch (Exception e) {
-                    throw (IOException) new 
IOException(e.getMessage()).initCause(e);
+                    throw new IOException(e.getMessage(), e);
                 }
             }
             LOGGER.trace("Update {}", cfgFile.getName());
             // update the cfg file
-            org.apache.felix.utils.properties.Properties properties = new 
org.apache.felix.utils.properties.Properties(cfgFile);
-            for (Enumeration<String> keys = props.keys(); 
keys.hasMoreElements(); ) {
-                String key = keys.nextElement();
-                if (!Constants.SERVICE_PID.equals(key)
-                        && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
-                        && !FILEINSTALL_FILE_NAME.equals(key)) {
-                    if (props.get(key) != null) {
-                        properties.put(key, props.get(key).toString());
+            if (!cfgFile.exists()) {
+                props.save(cfgFile);
+            } else {
+                org.apache.felix.utils.properties.Properties properties = new 
org.apache.felix.utils.properties.Properties(cfgFile);
+                for (String key : props.keySet()) {
+                    if (!Constants.SERVICE_PID.equals(key)
+                            && 
!ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
+                            && !FILEINSTALL_FILE_NAME.equals(key)) {
+                        List<String> comments = props.getComments(key);
+                        List<String> value = props.getRaw(key);
+                        if (!properties.containsKey(key)) {
+                            properties.put(key, comments, value);
+                        } else if (!append) {
+                            if (comments.isEmpty()) {
+                                comments = properties.getComments(key);
+                            }
+                            properties.put(key, comments, value);
+                        }
                     }
                 }
-            }
-            // remove "removed" properties from the cfg file
-            ArrayList<String> propertiesToRemove = new ArrayList<>();
-            for (String key : properties.keySet()) {
-                if (props.get(key) == null
-                        && !Constants.SERVICE_PID.equals(key)
-                        && !ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
-                        && !FILEINSTALL_FILE_NAME.equals(key)) {
-                    propertiesToRemove.add(key);
+                if (!append) {
+                    // remove "removed" properties from the cfg file
+                    ArrayList<String> propertiesToRemove = new ArrayList<>();
+                    for (String key : properties.keySet()) {
+                        if (!props.containsKey(key)
+                                && !Constants.SERVICE_PID.equals(key)
+                                && 
!ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)
+                                && !FILEINSTALL_FILE_NAME.equals(key)) {
+                            propertiesToRemove.add(key);
+                        }
+                    }
+                    for (String key : propertiesToRemove) {
+                        properties.remove(key);
+                    }
                 }
+                // save the cfg file
+                storage.mkdirs();
+                properties.save();
             }
-            for (String key : propertiesToRemove) {
-                properties.remove(key);
-            }
-
-            // save the cfg file
-            storage.mkdirs();
-            properties.save();
         }
     }
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/203cfd8d/features/core/src/test/java/org/apache/karaf/features/internal/model/ConfigTest.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/test/java/org/apache/karaf/features/internal/model/ConfigTest.java
 
b/features/core/src/test/java/org/apache/karaf/features/internal/model/ConfigTest.java
new file mode 100644
index 0000000..44cad3b
--- /dev/null
+++ 
b/features/core/src/test/java/org/apache/karaf/features/internal/model/ConfigTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.features.internal.model;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class ConfigTest {
+
+    @Test
+    public void testTrim() {
+        Config config = new Config();
+        config.setName("my.config");
+        config.setValue("    # my comment\n" +
+                        "    my.key = my.value\n");
+
+        assertEquals("my.value", config.getProperties().getProperty("my.key"));
+    }
+
+    @Test
+    public void testInterpolation() {
+        Config config = new Config();
+        config.setName("my.config");
+        config.setValue("    # my comment\n" +
+                        "    my.nb = 2\n" +
+                        "    my.key.1 = my.value.1\n" +
+                        "    my.key.2 = my.value.2\n" +
+                        "    my.key.3 = ab${my.key.${my.nb}}");
+
+        assertEquals("abmy.value.2", 
config.getProperties().getProperty("my.key.3"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/203cfd8d/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 bba1427..0227c21 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
@@ -46,6 +46,7 @@ import java.util.concurrent.Executors;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
+import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
@@ -65,6 +66,7 @@ import 
org.apache.karaf.features.internal.download.StreamProvider;
 import org.apache.karaf.features.internal.download.impl.DownloadManagerHelper;
 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.Dependency;
 import org.apache.karaf.features.internal.model.Feature;
@@ -158,6 +160,7 @@ public class Builder {
     String mavenRepositories;
     Map<String, String> config = new LinkedHashMap<>();
     Map<String, String> system = new LinkedHashMap<>();
+    List<String> pidsToExtract;
 
     private ScheduledExecutorService executor;
     private DownloadManager manager;
@@ -369,6 +372,11 @@ public class Builder {
         return this;
     }
 
+    public Builder pidsToExtract(List<String> pidsToExtract) {
+        this.pidsToExtract = pidsToExtract;
+        return this;
+    }
+
     /**
      * Specify a set of url mappings to use instead of
      * downloading from the original urls.
@@ -406,6 +414,10 @@ public class Builder {
         return blacklistPolicy;
     }
 
+    public List<String> getPidsToExtract() {
+        return pidsToExtract;
+    }
+
     public void generateAssembly() throws Exception {
         if (javase == null) {
             throw new IllegalArgumentException("javase is not set");
@@ -885,6 +897,19 @@ public class Builder {
                     installArtifact(downloader, 
configFile.getLocation().trim());
                 }
             }
+            // Extract configs
+            for (Config config : feature.getConfig()) {
+                if (pidMatching(config.getName())) {
+                    Files.write(etcDirectory.resolve(config.getName() + 
".cfg"), config.getValue().getBytes());
+                }
+            }
+            for (Conditional cond : feature.getConditional()) {
+                for (Config config : cond.getConfig()) {
+                    if (pidMatching(config.getName())) {
+                        Files.write(etcDirectory.resolve(config.getName() + 
".cfg"), config.getValue().getBytes());
+                    }
+                }
+            }
             // Install libraries
             List<String> libraries = new ArrayList<>();
             for (Library library : feature.getLibraries()) {
@@ -953,6 +978,113 @@ public class Builder {
         return allBootFeatures;
     }
 
+    private boolean pidMatching(String name) {
+        if (pidsToExtract == null) {
+            return true;
+        }
+        for (String p : pidsToExtract) {
+            boolean negated = false;
+            if (p.startsWith("!")) {
+                negated = true;
+                p = p.substring(1);
+            }
+            String r = globToRegex(p);
+            if (Pattern.matches(r, name)) {
+                return !negated;
+            }
+        }
+        return false;
+    }
+
+    private String globToRegex(String pattern) {
+        StringBuilder sb = new StringBuilder(pattern.length());
+        int inGroup = 0;
+        int inClass = 0;
+        int firstIndexInClass = -1;
+        char[] arr = pattern.toCharArray();
+        for (int i = 0; i < arr.length; i++) {
+            char ch = arr[i];
+            switch (ch) {
+                case '\\':
+                    if (++i >= arr.length) {
+                        sb.append('\\');
+                    } else {
+                        char next = arr[i];
+                        switch (next) {
+                            case ',':
+                                // escape not needed
+                                break;
+                            case 'Q':
+                            case 'E':
+                                // extra escape needed
+                                sb.append('\\');
+                            default:
+                                sb.append('\\');
+                        }
+                        sb.append(next);
+                    }
+                    break;
+                case '*':
+                    if (inClass == 0)
+                        sb.append(".*");
+                    else
+                        sb.append('*');
+                    break;
+                case '?':
+                    if (inClass == 0)
+                        sb.append('.');
+                    else
+                        sb.append('?');
+                    break;
+                case '[':
+                    inClass++;
+                    firstIndexInClass = i + 1;
+                    sb.append('[');
+                    break;
+                case ']':
+                    inClass--;
+                    sb.append(']');
+                    break;
+                case '.':
+                case '(':
+                case ')':
+                case '+':
+                case '|':
+                case '^':
+                case '$':
+                case '@':
+                case '%':
+                    if (inClass == 0 || (firstIndexInClass == i && ch == '^'))
+                        sb.append('\\');
+                    sb.append(ch);
+                    break;
+                case '!':
+                    if (firstIndexInClass == i)
+                        sb.append('^');
+                    else
+                        sb.append('!');
+                    break;
+                case '{':
+                    inGroup++;
+                    sb.append('(');
+                    break;
+                case '}':
+                    inGroup--;
+                    sb.append(')');
+                    break;
+                case ',':
+                    if (inGroup > 0)
+                        sb.append('|');
+                    else
+                        sb.append(',');
+                    break;
+                default:
+                    sb.append(ch);
+            }
+        }
+        return sb.toString();
+    }
+
     private String getRepos(Features rep) {
         StringBuilder repos = new StringBuilder();
         for (String repo : new HashSet<>(rep.getRepository())) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/203cfd8d/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
----------------------------------------------------------------------
diff --git 
a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
 
b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
index ee2c587..0bb61c8 100644
--- 
a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
+++ 
b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/AssemblyMojo.java
@@ -25,6 +25,7 @@ import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.attribute.PosixFilePermissions;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -232,6 +233,13 @@ public class AssemblyMojo extends MojoSupport {
     protected String propertyFileEdits;
 
     /**
+     * Glob specifying which configuration pids in the selected boot features
+     * should be extracted to the etc directory.
+     */
+    @Parameter
+    protected List<String> pidsToExtract = Collections.singletonList("*");
+
+    /**
      * Specify a set of translated urls to use instead of downloading the 
artifacts
      * from their original locations.  The given set will be extended with 
already
      * built artifacts from the maven project.
@@ -335,6 +343,7 @@ public class AssemblyMojo extends MojoSupport {
                 builder.propertyEdits(edits);
             }
         }
+        builder.pidsToExtract(pidsToExtract);
 
         Map<String, String> urls = new HashMap<>();
         List<Artifact> artifacts = new 
ArrayList<>(project.getAttachedArtifacts());

Reply via email to