Repository: karaf
Updated Branches:
  refs/heads/karaf-2.x 9da61ba12 -> 49a2c2bf4


[KARAF-2453] - Using features to extend existing configuration

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

Branch: refs/heads/karaf-2.x
Commit: 49a2c2bf431c7fd375994ff05b974a5154d0e4f6
Parents: 9da61ba
Author: anierbeck <[email protected]>
Authored: Sat Aug 23 20:32:44 2014 +0200
Committer: anierbeck <[email protected]>
Committed: Sat Aug 23 20:32:44 2014 +0200

----------------------------------------------------------------------
 .../features/command/InfoFeatureCommand.java    |   7 +-
 .../org/apache/karaf/features/Conditional.java  |   2 +-
 .../org/apache/karaf/features/ConfigInfo.java   |  30 +++
 .../java/org/apache/karaf/features/Feature.java |   3 +-
 .../karaf/features/FeaturesNamespaces.java      |   4 +-
 .../features/internal/ConditionalImpl.java      |   2 +-
 .../karaf/features/internal/ConfigInfoImpl.java |  61 +++++
 .../karaf/features/internal/ContentImpl.java    |   9 +-
 .../internal/FeatureValidationUtil.java         |   2 +
 .../features/internal/FeaturesServiceImpl.java  |  59 ++++-
 .../karaf/features/internal/RepositoryImpl.java |   4 +-
 .../karaf/features/karaf-features-1.2.1.xsd     | 262 +++++++++++++++++++
 .../apache/karaf/features/RepositoryTest.java   |  54 +++-
 .../org/apache/karaf/features/repo3.xml         |  35 +++
 .../features/management/codec/JmxFeature.java   |  14 +-
 .../webconsole/features/ExtendedFeature.java    |   4 +-
 16 files changed, 514 insertions(+), 38 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
----------------------------------------------------------------------
diff --git 
a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
 
b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
index 1f6b32d..fe4090a 100644
--- 
a/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
+++ 
b/features/command/src/main/java/org/apache/karaf/features/command/InfoFeatureCommand.java
@@ -27,6 +27,7 @@ import org.apache.felix.gogo.commands.Option;
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.ConfigInfo;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeaturesService;
 
@@ -153,13 +154,13 @@ public class InfoFeatureCommand extends 
FeaturesCommandSupport {
     }
 
     private void displayConfigInformation(Feature feature, String contentType) 
{
-        Map<String, Map<String, String>> configurations = 
feature.getConfigurations();
+               List<ConfigInfo> configurations = feature.getConfigurations();
         if (configurations.isEmpty()) {
             System.out.println(contentType + " has no configuration");
         } else {
             System.out.println(contentType + " configuration:");
-            for (String name : configurations.keySet()) {
-                System.out.println("  " + name);
+                       for (ConfigInfo configInf : configurations) {
+                               System.out.println("  " + configInf.getName());
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/Conditional.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/Conditional.java 
b/features/core/src/main/java/org/apache/karaf/features/Conditional.java
index 5a44e00..809bd28 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Conditional.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Conditional.java
@@ -27,7 +27,7 @@ public interface Conditional {
 
     List<BundleInfo> getBundles();
 
-    Map<String, Map<String, String>> getConfigurations();
+    List<ConfigInfo> getConfigurations();
 
     List<ConfigFileInfo> getConfigurationFiles();
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/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
new file mode 100644
index 0000000..b801f78
--- /dev/null
+++ b/features/core/src/main/java/org/apache/karaf/features/ConfigInfo.java
@@ -0,0 +1,30 @@
+/*
+ * 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;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+public interface ConfigInfo {
+
+       String getName();
+
+       Map<String, String> getProperties();
+
+       boolean isAppend();
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/Feature.java
----------------------------------------------------------------------
diff --git a/features/core/src/main/java/org/apache/karaf/features/Feature.java 
b/features/core/src/main/java/org/apache/karaf/features/Feature.java
index 37829da..0e3d068 100644
--- a/features/core/src/main/java/org/apache/karaf/features/Feature.java
+++ b/features/core/src/main/java/org/apache/karaf/features/Feature.java
@@ -17,7 +17,6 @@
 package org.apache.karaf.features;
 
 import java.util.List;
-import java.util.Map;
 
 /**
  * A feature is a list of bundles associated identified by its name.
@@ -44,7 +43,7 @@ public interface Feature {
 
     List<BundleInfo> getBundles();
 
-    Map<String, Map<String, String>> getConfigurations();
+    List<ConfigInfo> getConfigurations();
 
     List<ConfigFileInfo> getConfigurationFiles();
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/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 921c832..f8cd319 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
@@ -26,13 +26,15 @@ public interface FeaturesNamespaces {
     String URI_1_0_0 = "http://karaf.apache.org/xmlns/features/v1.0.0";;
     String URI_1_1_0 = "http://karaf.apache.org/xmlns/features/v1.1.0";;
     String URI_1_2_0 = "http://karaf.apache.org/xmlns/features/v1.2.0";;
+       String URI_1_2_1 = "http://karaf.apache.org/xmlns/features/v1.2.1";;
 
-    String URI_CURRENT = URI_1_2_0;
+       String URI_CURRENT = URI_1_2_1;
 
     QName FEATURES_0_0_0 = new QName("features");
     QName FEATURES_1_0_0 = new QName(URI_1_0_0, "features");
     QName FEATURES_1_1_0 = new QName(URI_1_1_0, "features");
     QName FEATURES_1_2_0 = new QName(URI_1_2_0, "features");
+       QName FEATURES_1_2_1 = new QName(URI_1_2_1, "features");
 
     QName FEATURES_CURRENT = FEATURES_1_1_0;
 

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/ConditionalImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/ConditionalImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/ConditionalImpl.java
index 78637aa..1b994ce 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/ConditionalImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/ConditionalImpl.java
@@ -39,7 +39,7 @@ public class ConditionalImpl extends ContentImpl implements 
Conditional {
         String conditionName = name + "-condition-" + 
getConditionId().replaceAll("[^A-Za-z0-9 ]", "_");
         FeatureImpl f = new FeatureImpl(conditionName, version);
         f.getBundles().addAll(getBundles());
-        f.getConfigurations().putAll(getConfigurations());
+               f.getConfigurations().addAll(getConfigurations());
         f.getConfigurationFiles().addAll(getConfigurationFiles());
         f.getDependencies().addAll(getDependencies());
         return f;

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/ConfigInfoImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/ConfigInfoImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/ConfigInfoImpl.java
new file mode 100644
index 0000000..a347506
--- /dev/null
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/ConfigInfoImpl.java
@@ -0,0 +1,61 @@
+/*
+ * 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;
+
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.karaf.features.ConfigInfo;
+
+public class ConfigInfoImpl implements ConfigInfo {
+
+       private String name;
+       private Map<String, String> properties;
+       private boolean append;
+
+       public ConfigInfoImpl(String name, Map<String, String> hashtable,
+                       String append) {
+               this.name = name;
+               this.properties = hashtable;
+               this.append = Boolean.parseBoolean(append);
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       public void setName(String name) {
+               this.name = name;
+       }
+
+       public Map<String, String> getProperties() {
+               return properties;
+       }
+
+       public void setProperties(Hashtable<String, String> properties) {
+               this.properties = properties;
+       }
+
+       public boolean isAppend() {
+               return append;
+       }
+
+       public void setAppend(boolean append) {
+               this.append = append;
+       }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/ContentImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/ContentImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/ContentImpl.java
index ac0bb19..a89cdf5 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/ContentImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/ContentImpl.java
@@ -23,13 +23,14 @@ import java.util.Map;
 
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.ConfigInfo;
 import org.apache.karaf.features.Feature;
 
 public class ContentImpl {
 
     private List<Feature> dependencies = new ArrayList<Feature>();
     private List<BundleInfo> bundles = new ArrayList<BundleInfo>();
-    private Map<String, Map<String,String>> configs = new HashMap<String, 
Map<String,String>>();
+       private List<ConfigInfo> configs = new ArrayList<ConfigInfo>();
     private List<ConfigFileInfo> configurationFiles = new 
ArrayList<ConfigFileInfo>();
 
     public List<Feature> getDependencies() {
@@ -40,7 +41,7 @@ public class ContentImpl {
         return bundles;
     }
 
-    public Map<String, Map<String, String>> getConfigurations() {
+       public List<ConfigInfo> getConfigurations() {
         return configs;
     }
 
@@ -56,8 +57,8 @@ public class ContentImpl {
         bundles.add(bundle);
     }
 
-    public void addConfig(String name, Map<String,String> properties) {
-        configs.put(name, properties);
+       public void addConfig(ConfigInfo configInfo) {
+               configs.add(configInfo);
     }
 
     public void addConfigurationFile(ConfigFileInfo configurationFileInfo) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
index 09e6342..d00cabb 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/FeatureValidationUtil.java
@@ -68,6 +68,8 @@ public class FeatureValidationUtil {
             validate(doc, 
"/org/apache/karaf/features/karaf-features-1.1.0.xsd");
         } else if (FeaturesNamespaces.FEATURES_1_2_0.equals(name)) {
             validate(doc, 
"/org/apache/karaf/features/karaf-features-1.2.0.xsd");
+        } else if (FeaturesNamespaces.FEATURES_1_2_1.equals(name)) {
+            validate(doc, 
"/org/apache/karaf/features/karaf-features-1.2.1.xsd");
         }
         else {
             throw new IllegalArgumentException("Unrecognized root element: " + 
name);

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
index 0c927a7..caf23c8 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/FeaturesServiceImpl.java
@@ -43,6 +43,7 @@ import java.util.LinkedHashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Properties;
 import java.util.Set;
 import java.util.TreeSet;
@@ -54,6 +55,7 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 
+import org.apache.felix.utils.collections.MapToDictionary;
 import org.apache.felix.utils.manifest.Clause;
 import org.apache.felix.utils.manifest.Parser;
 import org.apache.felix.utils.version.VersionRange;
@@ -61,6 +63,7 @@ import org.apache.felix.utils.version.VersionTable;
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.ConfigInfo;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.FeatureEvent;
 import org.apache.karaf.features.FeaturesListener;
@@ -600,19 +603,41 @@ public class FeaturesServiceImpl implements 
FeaturesService, FrameworkListener {
                 }
             }
         }
-        for (String config : feature.getConfigurations().keySet()) {
-            Dictionary<String, String> props = new Hashtable<String, 
String>(feature.getConfigurations().get(config));
-            String[] pid = parsePid(config);
-            Configuration cfg = findExistingConfiguration(configAdmin, pid[0], 
pid[1]);
-            if (cfg == null) {
-                cfg = createConfiguration(configAdmin, pid[0], pid[1]);
-                String key = createConfigurationKey(pid[0], pid[1]);
-                props.put(CONFIG_KEY, key);
-                if (cfg.getBundleLocation() != null) {
-                    cfg.setBundleLocation(null);
-                }
-                cfg.update(props);
-            }
+               for (ConfigInfo config : feature.getConfigurations()) {
+                       String name = config.getName();
+                       Map<String, String> props = config.getProperties();
+
+                       String[] pid = parsePid(config.getName());
+                       Configuration cfg = 
findExistingConfiguration(configAdmin, pid[0],
+                                       pid[1]);
+                       if (cfg == null) {
+                               
+                               Dictionary<String, String> cfgProps = 
convertToDict(config
+                                               .getProperties());
+
+                               cfg = createConfiguration(configAdmin, pid[0], 
pid[1]);
+                               String key = createConfigurationKey(pid[0], 
pid[1]);
+                               cfgProps.put(CONFIG_KEY, key);
+                               if (cfg.getBundleLocation() != null) {
+                                       cfg.setBundleLocation(null);
+                               }
+                               cfg.update(cfgProps);
+                       } else if (config.isAppend()) {
+                               Dictionary<String, Object> properties = 
cfg.getProperties();
+                               for (Enumeration<String> propKeys = 
properties.keys(); propKeys
+                                               .hasMoreElements();) {
+                                       String key = propKeys.nextElement();
+                                       // remove existing entry, since it's 
about appending.
+                                       if (props.containsKey(key)) {
+                                               props.remove(key);
+                                       }
+                               }
+                               if (props.size() > 0) {
+                                       // convert props to dictionary
+                                       Dictionary<String, String> cfgProps = 
convertToDict(props);
+                                       cfg.update(cfgProps);
+                               }
+                       }
         }
         for (ConfigFileInfo configFile : feature.getConfigurationFiles()) {
             installConfigurationFile(configFile.getLocation(),
@@ -643,6 +668,14 @@ public class FeaturesServiceImpl implements 
FeaturesService, FrameworkListener {
 
     }
 
+       private Dictionary<String, String> convertToDict(Map<String, String> 
props) {
+               Dictionary<String, String> cfgProps = new Hashtable<String, 
String>();
+               for (Entry<String, String> property : props.entrySet()) {
+                       cfgProps.put(property.getKey(), property.getValue());
+               }
+               return cfgProps;
+       }
+
     private String createConfigurationKey(String pid, String factoryPid) {
         return factoryPid == null ? pid : pid + "-" + factoryPid;
     }

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
 
b/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
index c0fc354..a730bb3 100644
--- 
a/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
+++ 
b/features/core/src/main/java/org/apache/karaf/features/internal/RepositoryImpl.java
@@ -214,7 +214,9 @@ public class RepositoryImpl implements Repository {
             Element c = (Element) configNodes.item(j);
             if (c.getParentNode() != e) continue;
             String cfgName = c.getAttribute("name");
+            String append = c.getAttribute("append");
             String data = c.getTextContent();
+            
             Properties properties = new Properties();
             properties.load(new ByteArrayInputStream(data.getBytes()));
             interpolation(properties);
@@ -223,7 +225,7 @@ public class RepositoryImpl implements Repository {
                 String n = key.toString();
                 hashtable.put(n, properties.getProperty(n));
             }
-            content.addConfig(cfgName, hashtable);
+                       content.addConfig(new ConfigInfoImpl(cfgName, 
hashtable, append));
         }
         NodeList configurationFiles = e.getElementsByTagName("configfile");
         for (int j = 0; j < configurationFiles.getLength(); j++) {

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.2.1.xsd
----------------------------------------------------------------------
diff --git 
a/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.2.1.xsd
 
b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.2.1.xsd
new file mode 100644
index 0000000..5044073
--- /dev/null
+++ 
b/features/core/src/main/resources/org/apache/karaf/features/karaf-features-1.2.1.xsd
@@ -0,0 +1,262 @@
+<?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.2.1";
+    xmlns:tns="http://karaf.apache.org/xmlns/features/v1.2.1";
+    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 repositories where dependencies 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: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:choice>
+        <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="resolver" type="tns:resolver">
+            <xs:annotation>
+                <xs:documentation><![CDATA[
+Optional alternative resolver to use for determining the list of bundles to 
install for a given feature.
+                ]]>
+                </xs:documentation>
+            </xs:annotation>
+        </xs:attribute>
+        <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: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: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: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: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="resolver">
+        <xs:annotation>
+            <xs:documentation><![CDATA[
+Resolver to use. Karaf will look for OSGi service which have following 
properties:
+objectClass: org.apache.karaf.features.Resolver
+name: the value
+            ]]></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/49a2c2bf/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
----------------------------------------------------------------------
diff --git 
a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java 
b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
index 2e1c6da..f3312ea 100644
--- a/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
+++ b/features/core/src/test/java/org/apache/karaf/features/RepositoryTest.java
@@ -19,8 +19,9 @@ package org.apache.karaf.features;
 import java.net.URI;
 
 import junit.framework.TestCase;
-import org.apache.karaf.features.internal.RepositoryImpl;
+
 import org.apache.karaf.features.internal.FeatureImpl;
+import org.apache.karaf.features.internal.RepositoryImpl;
 
 
 public class RepositoryTest extends TestCase {
@@ -40,9 +41,54 @@ public class RepositoryTest extends TestCase {
         assertEquals("f1", features[0].getName());
         assertNotNull(features[0].getConfigurations());
         assertEquals(1, features[0].getConfigurations().size());
-        assertNotNull(features[0].getConfigurations().get("c1"));
-        assertEquals(1, features[0].getConfigurations().get("c1").size());
-        assertEquals("v", features[0].getConfigurations().get("c1").get("k"));
+        assertNotNull(features[0].getConfigurations().get(0).getName());
+               assertEquals("c1", 
features[0].getConfigurations().get(0).getName());
+        assertEquals(1, 
features[0].getConfigurations().get(0).getProperties().size());
+        assertEquals("v", 
features[0].getConfigurations().get(0).getProperties().get("k"));
+        assertNotNull(features[0].getDependencies());
+        assertEquals(0, features[0].getDependencies().size());
+        assertNotNull(features[0].getBundles());
+        assertEquals(2, features[0].getBundles().size());
+        assertEquals("b1", features[0].getBundles().get(0).getLocation());
+        assertEquals("b2", features[0].getBundles().get(1).getLocation());
+        assertNotNull(features[1]);
+        assertEquals("f2", features[1].getName());
+        assertNotNull(features[1].getConfigurations());
+        assertEquals(0, features[1].getConfigurations().size());
+        assertNotNull(features[1].getDependencies());
+        assertEquals(1, features[1].getDependencies().size());
+        assertEquals("f1" + FeatureImpl.SPLIT_FOR_NAME_AND_VERSION + 
FeatureImpl.DEFAULT_VERSION, features[1].getDependencies().get(0).toString());
+        assertNotNull(features[1].getBundles());
+        assertEquals(1, features[1].getBundles().size());
+        assertEquals("b3", features[1].getBundles().get(0).getLocation());
+        assertEquals("f3", features[2].getName());
+        assertNotNull(features[2].getConfigurationFiles());
+        assertEquals(1, features[2].getConfigurationFiles().size());
+        assertEquals("cf1", 
features[2].getConfigurationFiles().get(0).getFinalname());
+        assertEquals("cfloc", 
features[2].getConfigurationFiles().get(0).getLocation());
+    }
+
+    
+    public void testLoadConfigAppend() throws Exception {
+        RepositoryImpl r = new 
RepositoryImpl(getClass().getResource("repo3.xml").toURI());
+        // Check repo
+        URI[] repos = r.getRepositories();
+        assertNotNull(repos);
+        assertEquals(1, repos.length);
+        assertEquals(URI.create("urn:r1"), repos[0]);
+        // Check features
+        Feature[] features = r.getFeatures();
+        assertNotNull(features);
+        assertEquals(3, features.length);
+        assertNotNull(features[0]);
+        assertEquals("f1", features[0].getName());
+        assertNotNull(features[0].getConfigurations());
+        assertEquals(1, features[0].getConfigurations().size());
+        assertNotNull(features[0].getConfigurations().get(0).getName());
+               assertEquals("c1", 
features[0].getConfigurations().get(0).getName());
+        assertEquals(1, 
features[0].getConfigurations().get(0).getProperties().size());
+        assertEquals("v", 
features[0].getConfigurations().get(0).getProperties().get("k"));
+        assertTrue(features[0].getConfigurations().get(0).isAppend());
         assertNotNull(features[0].getDependencies());
         assertEquals(0, features[0].getDependencies().size());
         assertNotNull(features[0].getBundles());

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
----------------------------------------------------------------------
diff --git 
a/features/core/src/test/resources/org/apache/karaf/features/repo3.xml 
b/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
new file mode 100644
index 0000000..8f06ffa
--- /dev/null
+++ b/features/core/src/test/resources/org/apache/karaf/features/repo3.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<features name="test" xmlns="http://karaf.apache.org/xmlns/features/v1.2.1";>
+    <repository>urn:r1</repository>
+    <feature name="f1" region="foo">
+        <config name="c1" append="true">
+            k=v
+        </config>
+        <bundle>b1</bundle>
+        <bundle>b2</bundle>
+    </feature>
+    <feature name="f2">
+        <feature>f1</feature>
+        <bundle>b3</bundle>
+    </feature>
+    <feature name="f3">
+       <configfile finalname="cf1" override="true">cfloc</configfile>
+       <bundle>b4</bundle> 
+    </feature>
+</features>

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/features/management/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
----------------------------------------------------------------------
diff --git 
a/features/management/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
 
b/features/management/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
index ad682be..12f920b 100644
--- 
a/features/management/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
+++ 
b/features/management/src/main/java/org/apache/karaf/features/management/codec/JmxFeature.java
@@ -35,6 +35,7 @@ import javax.management.openmbean.TabularDataSupport;
 
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.ConfigInfo;
 import org.apache.karaf.features.Feature;
 import org.apache.karaf.features.management.FeaturesServiceMBean;
 
@@ -76,7 +77,7 @@ public class JmxFeature {
             itemValues[1] = feature.getVersion();
             itemValues[2] = 
getFeatureIdentifierTable(feature.getDependencies());
             itemValues[3] = getBundleUris(feature.getBundles());
-            itemValues[4] = getConfigTable(feature.getConfigurations());
+            itemValues[4] = getConfigList(feature.getConfigurations());
             itemValues[5] = getConfigFileList(feature.getConfigurationFiles());
             itemValues[6] = installed;
             data = new CompositeDataSupport(FEATURE, itemNames, itemValues);
@@ -122,13 +123,14 @@ public class JmxFeature {
         return array;
     }
 
-    static TabularData getConfigTable(Map<String, Map<String, String>> 
configs) throws OpenDataException {
+    static TabularData getConfigList(List<ConfigInfo> configs) throws 
OpenDataException {
         TabularDataSupport table = new 
TabularDataSupport(FEATURE_CONFIG_TABLE);
-        for (Map.Entry<String, Map<String, String>> entry : 
configs.entrySet()) {
+               for (ConfigInfo configInfo : configs) {
             String[] itemNames = FeaturesServiceMBean.FEATURE_CONFIG;
-            Object[] itemValues = new Object[2];
-            itemValues[0] = entry.getKey();
-            itemValues[1] = getConfigElementTable(entry.getValue());
+                       Object[] itemValues = new Object[3];
+                       itemValues[0] = configInfo.getName();
+                       itemValues[1] = 
getConfigElementTable(configInfo.getProperties());
+                       itemValues[2] = configInfo.isAppend();
             CompositeData config = new CompositeDataSupport(FEATURE_CONFIG, 
itemNames, itemValues);
             table.put(config);
         }

http://git-wip-us.apache.org/repos/asf/karaf/blob/49a2c2bf/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
----------------------------------------------------------------------
diff --git 
a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
 
b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
index 568f231..748b0ed 100644
--- 
a/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
+++ 
b/webconsole/features/src/main/java/org/apache/karaf/webconsole/features/ExtendedFeature.java
@@ -17,11 +17,11 @@
 package org.apache.karaf.webconsole.features;
 
 import java.util.List;
-import java.util.Map;
 
 import org.apache.karaf.features.BundleInfo;
 import org.apache.karaf.features.Conditional;
 import org.apache.karaf.features.ConfigFileInfo;
+import org.apache.karaf.features.ConfigInfo;
 import org.apache.karaf.features.Feature;
 
 public class ExtendedFeature implements Feature {
@@ -51,7 +51,7 @@ public class ExtendedFeature implements Feature {
         return this.feature.getBundles();
     }
 
-    public Map<String, Map<String, String>> getConfigurations() {
+       public List<ConfigInfo> getConfigurations() {
         return this.feature.getConfigurations();
     }
 

Reply via email to