Merge pull request #390 from jbonofre/KARAF-5430

[KARAF-5430] Upgrade to Spring 4.0.9.RELEASE and 4.3.12.RELEASE
diff --git a/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java b/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
index acca105..aa94600 100644
--- a/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
+++ b/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundleCommand.java
@@ -16,9 +16,11 @@
  */
 package org.apache.karaf.bundle.command;
 
+import org.apache.karaf.bundle.command.completers.BundleSymbolicNameCompleter;
 import org.apache.karaf.bundle.core.BundleService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Completion;
 import org.apache.karaf.shell.api.action.Option;
 import org.apache.karaf.shell.api.action.lifecycle.Reference;
 import org.osgi.framework.Bundle;
@@ -33,6 +35,7 @@
     String context = "0";
 
     @Argument(index = 0, name = "id", description = "The bundle ID or name or name/version", required = true, multiValued = false)
+    @Completion(BundleSymbolicNameCompleter.class)
     String id;
 
     @Reference
diff --git a/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java b/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
index 029c144..e9c3a9c 100644
--- a/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
+++ b/bundle/core/src/main/java/org/apache/karaf/bundle/command/BundlesCommand.java
@@ -19,9 +19,11 @@
 import java.util.ArrayList;
 import java.util.List;
 
+import org.apache.karaf.bundle.command.completers.BundleSymbolicNameCompleter;
 import org.apache.karaf.bundle.core.BundleService;
 import org.apache.karaf.shell.api.action.Action;
 import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Completion;
 import org.apache.karaf.shell.api.action.Option;
 import org.apache.karaf.shell.api.action.lifecycle.Reference;
 import org.apache.karaf.shell.support.MultiException;
@@ -34,6 +36,7 @@
     String context = "0";
 
     @Argument(index = 0, name = "ids", description = "The list of bundle (identified by IDs or name or name/version) separated by whitespaces", required = false, multiValued = true)
+    @Completion(BundleSymbolicNameCompleter.class)
     List<String> ids;
     
     @Reference
diff --git a/bundle/core/src/main/java/org/apache/karaf/bundle/command/completers/BundleSymbolicNameCompleter.java b/bundle/core/src/main/java/org/apache/karaf/bundle/command/completers/BundleSymbolicNameCompleter.java
new file mode 100644
index 0000000..0b2ec36
--- /dev/null
+++ b/bundle/core/src/main/java/org/apache/karaf/bundle/command/completers/BundleSymbolicNameCompleter.java
@@ -0,0 +1,45 @@
+/*
+ * 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.bundle.command.completers;
+
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.apache.karaf.shell.api.console.CommandLine;
+import org.apache.karaf.shell.api.console.Completer;
+import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.completers.StringsCompleter;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+import java.util.List;
+
+@Service
+public class BundleSymbolicNameCompleter implements Completer {
+
+    @Reference
+    private BundleContext bundleContext;
+
+    @Override
+    public int complete(Session session, CommandLine commandLine, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        for (Bundle bundle : bundleContext.getBundles()) {
+            delegate.getStrings().add(bundle.getSymbolicName());
+        }
+        return delegate.complete(session, commandLine, candidates);
+    }
+
+}
diff --git a/config/src/main/java/org/apache/karaf/config/command/UpdateCommand.java b/config/src/main/java/org/apache/karaf/config/command/UpdateCommand.java
index a908c8c..ff572a7 100644
--- a/config/src/main/java/org/apache/karaf/config/command/UpdateCommand.java
+++ b/config/src/main/java/org/apache/karaf/config/command/UpdateCommand.java
@@ -29,7 +29,7 @@
     protected Object doExecute() throws Exception {
         TypedProperties props = getEditedProps();
         if (props == null) {
-            System.err.println("No configuration is being edited--run the edit command first");
+            System.err.println("No configuration is being edited. Run the edit command first.");
             return null;
         }
 
diff --git a/config/src/main/java/org/apache/karaf/config/command/completers/ConfigurationCompleter.java b/config/src/main/java/org/apache/karaf/config/command/completers/ConfigurationCompleter.java
index 19cc0df..b0cef13 100644
--- a/config/src/main/java/org/apache/karaf/config/command/completers/ConfigurationCompleter.java
+++ b/config/src/main/java/org/apache/karaf/config/command/completers/ConfigurationCompleter.java
@@ -77,7 +77,7 @@
 
         Collection<String> pids = new ArrayList<>();
         for (Configuration config : configs) {
-            pids.add(config.getPid() + " ");
+            pids.add(config.getPid());
         }
 
         delegate.getStrings().addAll(pids);
@@ -95,9 +95,9 @@
     public void configurationEvent(ConfigurationEvent configurationEvent) {
         String pid = configurationEvent.getPid();
         if (configurationEvent.getType() == ConfigurationEvent.CM_DELETED) {
-            delegate.getStrings().remove(pid + " ");
+            delegate.getStrings().remove(pid);
         } else if (configurationEvent.getType() == ConfigurationEvent.CM_UPDATED) {
-            delegate.getStrings().add(pid + " ");
+            delegate.getStrings().add(pid);
         }
     }
 }
diff --git a/config/src/main/java/org/apache/karaf/config/core/impl/ConfigRepositoryImpl.java b/config/src/main/java/org/apache/karaf/config/core/impl/ConfigRepositoryImpl.java
index b2d05c8..898c88c 100644
--- a/config/src/main/java/org/apache/karaf/config/core/impl/ConfigRepositoryImpl.java
+++ b/config/src/main/java/org/apache/karaf/config/core/impl/ConfigRepositoryImpl.java
@@ -24,8 +24,10 @@
 import java.net.URL;
 import java.util.Dictionary;
 import java.util.Enumeration;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.Map;
+import java.util.Set;
 
 import org.apache.felix.utils.properties.TypedProperties;
 import org.apache.karaf.config.core.ConfigRepository;
@@ -55,19 +57,24 @@
     public void update(String pid, Map<String, Object> properties) throws IOException {
         try {
             LOGGER.trace("Updating configuration {}", pid);
-            Configuration cfg = configAdmin.getConfiguration(pid, null);
+            Configuration cfg = configAdmin.getConfiguration(pid, "?");
             Dictionary<String, Object> dict = cfg.getProperties();
             TypedProperties props = new TypedProperties();
             File file = getCfgFileFromProperties(dict);
             if (file != null) {
                 props.load(file);
                 props.putAll(properties);
+                props.keySet().retainAll(properties.keySet());
                 props.save(file);
                 props.clear();
                 props.load(file);
                 props.put(FILEINSTALL_FILE_NAME, file.toURI().toString());
             } else {
+                file = new File(System.getProperty("karaf.etc"), pid + ".cfg");
                 props.putAll(properties);
+                props.keySet().retainAll(properties.keySet());
+                props.save(file);
+                props.put(FILEINSTALL_FILE_NAME, file.toURI().toString());
             }
             cfg.update(new Hashtable<>(props));
         } catch (URISyntaxException e) {
@@ -146,7 +153,12 @@
     @Override
     public String createFactoryConfiguration(String factoryPid, String alias, Map<String, Object> properties) throws IOException {
         Configuration config = configAdmin.createFactoryConfiguration(factoryPid, "?");
-        config.update(new Hashtable<>(properties));
+        TypedProperties props = new TypedProperties();
+        File file = File.createTempFile(factoryPid + "-", ".cfg", new File(System.getProperty("karaf.etc")));
+        props.putAll(properties);
+        props.save(file);
+        props.put(FILEINSTALL_FILE_NAME, file.toURI().toString());
+        config.update(new Hashtable<>(props));
         return config.getPid();
     }
 
diff --git a/itests/src/test/java/org/apache/karaf/itests/BundleTest.java b/itests/src/test/java/org/apache/karaf/itests/BundleTest.java
index 38b270f..2fd336d 100644
--- a/itests/src/test/java/org/apache/karaf/itests/BundleTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/BundleTest.java
@@ -62,7 +62,7 @@
         assertFalse(allCapabilitiesOutput.isEmpty());
         String jmxWhiteboardBundleCapabilitiesOutput = executeCommand("bundle:capabilities org.apache.aries.jmx.whiteboard", ADMIN_ROLES);
         System.out.println(jmxWhiteboardBundleCapabilitiesOutput);
-        assertTrue(jmxWhiteboardBundleCapabilitiesOutput.contains("osgi.wiring.bundle; org.apache.aries.jmx.whiteboard 1.1.5 [UNUSED]"));
+        assertContains("osgi.wiring.bundle; org.apache.aries.jmx.whiteboard 1.1.5 [UNUSED]", jmxWhiteboardBundleCapabilitiesOutput);
     }
 
     @Test
@@ -71,7 +71,7 @@
         assertFalse(allClassesOutput.isEmpty());
         String jmxWhiteboardBundleClassesOutput = executeCommand("bundle:classes org.apache.aries.jmx.whiteboard", ADMIN_ROLES);
         System.out.println(jmxWhiteboardBundleClassesOutput);
-        assertTrue(jmxWhiteboardBundleClassesOutput.contains("org/apache/aries/jmx/whiteboard/Activator$MBeanTracker.class"));
+        assertContains("org/apache/aries/jmx/whiteboard/Activator$MBeanTracker.class", jmxWhiteboardBundleClassesOutput);
     }
 
     /**
@@ -94,14 +94,14 @@
     public void headersCommand() throws Exception {
         String headersOutput = executeCommand("bundle:headers org.apache.aries.jmx.whiteboard", ADMIN_ROLES);
         System.out.println(headersOutput);
-        assertTrue(headersOutput.contains("Bundle-Activator = org.apache.aries.jmx.whiteboard.Activator"));
+        assertContains("Bundle-Activator = org.apache.aries.jmx.whiteboard.Activator", headersOutput);
     }
 
     @Test
     public void infoCommand() throws Exception {
         String infoOutput = executeCommand("bundle:info org.apache.karaf.management.server", ADMIN_ROLES);
         System.out.println(infoOutput);
-        assertTrue(infoOutput.contains("This bundle starts the Karaf embedded MBean server"));
+        assertContains("This bundle starts the Karaf embedded MBean server", infoOutput);
     }
 
     @Test
diff --git a/itests/src/test/java/org/apache/karaf/itests/ssh/ConfigSshCommandSecurityTest.java b/itests/src/test/java/org/apache/karaf/itests/ssh/ConfigSshCommandSecurityTest.java
index 2e012c9..7d627be 100644
--- a/itests/src/test/java/org/apache/karaf/itests/ssh/ConfigSshCommandSecurityTest.java
+++ b/itests/src/test/java/org/apache/karaf/itests/ssh/ConfigSshCommandSecurityTest.java
@@ -66,27 +66,26 @@
         String result = assertCommand(user, "config:edit " + pid + "\n" +
                 "config:property-list\n" +
                 "config:cancel", Result.OK);
-        Assert.assertTrue("Result should contain 'x = yz': " + result, result.contains("x = yz"));
-        Assert.assertTrue("Result should contain 'a = b': " + result, result.contains("a = b"));
+        assertContains("a = b", result);
         String result2 = assertCommand(user, "config:edit " + pid + "\n" +
                 "config:property-delete a\n" +
                 "config:property-list\n" +
                 "config:update", Result.OK);
-        Assert.assertTrue("Result should contain 'x = yz': " + result2, result2.contains("x = yz"));
-        Assert.assertFalse("Result should contain 'a = b': " + result2, result2.contains("a = b"));
+        assertContains("x = yz", result2);
+        assertContainsNot("a = b", result2);
 
         if (isAdmin) {
             assertCommand(user, "config:delete " + pid, Result.OK);
             String result3 = assertCommand(user, "config:edit " + pid + "\n" +
                     "config:property-list", Result.OK);
-            Assert.assertFalse(result3.contains("x = yz"));
-            Assert.assertFalse(result3.contains("a = b"));
+            assertContainsNot("x = yz", result3);
+            assertContainsNot("a = b", result3);
         } else {
             assertCommand(user, "config:delete " + pid, Result.NOT_FOUND);
             String result3 = assertCommand(user, "config:edit " + pid + "\n" +
                     "config:property-list", Result.OK);
-            Assert.assertTrue("The delete command should have had no effect", result3.contains("x = yz"));
-            Assert.assertFalse(result3.contains("a = b"));
+            assertContains("x = yz", result3);
+            assertContainsNot("a = b", result3);
         }
     }
 
@@ -123,11 +122,11 @@
         assertCommand(user, "config:property-append -p " + pid + " a.b.c .g.h", expectedResult);
 
         if (expectedResult == Result.OK) {
-            Assert.assertTrue(assertCommand(user, "config:property-list -p " + pid, Result.OK).contains("a.b.c = d.e.f.g.h"));
+            assertContains("a.b.c = d.e.f.g.h", assertCommand(user, "config:property-list -p " + pid, Result.OK));
         }
         assertCommand(user, "config:property-delete -p " + pid + " a.b.c", expectedResult);
         if (expectedResult == Result.OK) {
-            Assert.assertFalse(assertCommand(user, "config:property-list -p " + pid, Result.OK).contains("a.b.c"));
+            assertContainsNot("a.b.c", assertCommand(user, "config:property-list -p " + pid, Result.OK));
         }
     }
 }
diff --git a/jms/src/main/java/org/apache/karaf/jms/command/completers/ConnectionFactoriesNameCompleter.java b/jms/src/main/java/org/apache/karaf/jms/command/completers/ConnectionFactoriesNameCompleter.java
index 2fd8d2a..98f97b3 100644
--- a/jms/src/main/java/org/apache/karaf/jms/command/completers/ConnectionFactoriesNameCompleter.java
+++ b/jms/src/main/java/org/apache/karaf/jms/command/completers/ConnectionFactoriesNameCompleter.java
@@ -40,7 +40,7 @@
         StringsCompleter delegate = new StringsCompleter();
         try {
             for (String connectionFactory : jmsService.connectionFactories()) {
-                delegate.getStrings().add(connectionFactory + " ");
+                delegate.getStrings().add(connectionFactory);
             }
         } catch (Exception e) {
             // nothing to do
diff --git a/pom.xml b/pom.xml
index 8d554b5..2531a21 100644
--- a/pom.xml
+++ b/pom.xml
@@ -189,7 +189,7 @@
         <felix.configadmin.version>1.8.16</felix.configadmin.version>
         <felix.connect.version>0.1.0</felix.connect.version>
         <felix.coordinator.version>1.0.2</felix.coordinator.version>
-        <felix.fileinstall.version>3.6.2</felix.fileinstall.version>
+        <felix.fileinstall.version>3.6.4</felix.fileinstall.version>
         <felix.framework.version>5.6.8</felix.framework.version>
         <felix.framework.security.version>2.6.0</felix.framework.security.version>
         <felix.gogo.runtime.version>1.0.10</felix.gogo.runtime.version>
@@ -197,7 +197,7 @@
         <felix.httplite.version>0.1.6</felix.httplite.version>
         <felix.inventory.version>1.0.4</felix.inventory.version>
         <felix.plugin.version>3.3.0</felix.plugin.version>
-        <felix.utils.version>1.10.2</felix.utils.version>
+        <felix.utils.version>1.10.4</felix.utils.version>
         <felix.webconsole.version>4.3.4</felix.webconsole.version>
         <felix.webconsole.api.version>3.1.2</felix.webconsole.api.version>
         <felix.metatype.version>1.1.4</felix.metatype.version>
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
index 25e259e..0a4a5e1 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/action/command/ArgumentCompleter.java
@@ -22,6 +22,7 @@
 import java.lang.reflect.Field;
 import java.net.URI;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.EnumSet;
 import java.util.HashMap;
@@ -119,12 +120,10 @@
                 if (ann != null) {
                     Class<?> clazz = ann.value();
                     String[] value = ann.values();
-                    if (clazz != null) {
-                        if (value.length > 0 && clazz == StringsCompleter.class) {
-                            completer = new StringsCompleter(value, ann.caseSensitive());
-                        } else {
-                            completer = command.getCompleter(clazz);
-                        }
+                    if (value.length > 0) {
+                        completer = new StringsCompleter(Arrays.asList(value), ann.caseSensitive());
+                    } else {
+                        completer = command.getCompleter(clazz);
                     }
                 } else {
                     completer = getDefaultCompleter(field, multi);
@@ -148,12 +147,10 @@
                     try {
                         Class clazz = ann.value();
                         String[] value = ann.values();
-                        if (clazz != null) {
-                            if (clazz == StringsCompleter.class) {
-                                completer = new StringsCompleter(value, ann.caseSensitive());
-                            } else {
-                                completer = command.getCompleter(clazz);
-                            }
+                        if (value.length > 0) {
+                            completer = new StringsCompleter(Arrays.asList(value), ann.caseSensitive());
+                        } else {
+                            completer = command.getCompleter(clazz);
                         }
                     } catch (Throwable t) {
                         // Ignore in case the completer class is not even available
@@ -189,13 +186,13 @@
         } else if (type.isAssignableFrom(File.class)) {
             completer = new FileCompleter();
         } else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)) {
-            completer = new StringsCompleter(new String[] {"false", "true"}, false);
+            completer = new StringsCompleter(Arrays.asList("false", "true"));
         } else if (Enum.class.isAssignableFrom(type)) {
             Set<String> values = new HashSet<>();
             for (Object o : EnumSet.allOf((Class<Enum>) type)) {
                 values.add(o.toString());
             }
-            completer = new StringsCompleter(values, false);
+            completer = new StringsCompleter(values);
         }
         return completer;
     }
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
index 84f87c6..b75489b 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/CommandsCompleter.java
@@ -97,7 +97,7 @@
                 }
             }
             if (!subShell.equals(Session.SCOPE_GLOBAL)) {
-                completers.add(new StringsCompleter(new String[] { "exit" }));
+                completers.add(new StringsCompleter(Collections.singletonList("exit")));
             }
             completers.forEach(c -> c.completeCandidates(session, commandLine, candidates));
             return;
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
index 7eff78d..4a57aef 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/completers/StringsCompleter.java
@@ -39,7 +39,7 @@
     private final boolean caseSensitive;
 
     public StringsCompleter() {
-        this(true);
+        this(false);
     }
 
     public StringsCompleter(final boolean caseSensitive) {
@@ -95,7 +95,7 @@
             }
 
             // noinspection unchecked
-            candidates.add(match);
+            candidates.add(match + " ");
         }
 
         return candidates.isEmpty() ? -1 : commandLine.getBufferPosition() - commandLine.getArgumentPosition();