Merge pull request #1032 from jbonofre/KARAF-6563

[KARAF-6563] Upgrade to maven-site-plugin 3.8.2
diff --git a/archetypes/assembly/pom.xml b/archetypes/assembly/pom.xml
index 1279ff0..9ab6f71 100644
--- a/archetypes/assembly/pom.xml
+++ b/archetypes/assembly/pom.xml
@@ -34,7 +34,7 @@
     <description>This archetype sets up an empty karaf assembly project.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/archetypes/blueprint/pom.xml b/archetypes/blueprint/pom.xml
index bd23f82..a790148 100644
--- a/archetypes/blueprint/pom.xml
+++ b/archetypes/blueprint/pom.xml
@@ -35,7 +35,7 @@
     <description>An archetype for creating a simple blueprint bundle.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/archetypes/bundle/pom.xml b/archetypes/bundle/pom.xml
index e10f522..99973ce 100644
--- a/archetypes/bundle/pom.xml
+++ b/archetypes/bundle/pom.xml
@@ -35,7 +35,7 @@
     <description>A simple bundle archetype.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/archetypes/command/pom.xml b/archetypes/command/pom.xml
index cfbbeaa..c2111bf 100644
--- a/archetypes/command/pom.xml
+++ b/archetypes/command/pom.xml
@@ -35,7 +35,7 @@
     <description>A Karaf command archetype.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/archetypes/feature/pom.xml b/archetypes/feature/pom.xml
index 087af6d..da68913 100644
--- a/archetypes/feature/pom.xml
+++ b/archetypes/feature/pom.xml
@@ -35,7 +35,7 @@
     <description>This archetype sets up an empty karaf features project.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/archetypes/kar/pom.xml b/archetypes/kar/pom.xml
index 3695061..c5b9bc9 100644
--- a/archetypes/kar/pom.xml
+++ b/archetypes/kar/pom.xml
@@ -35,7 +35,7 @@
     <description>This archetype sets up an empty karaf kar project.</description>
 
     <properties>
-        <archetype.version>3.0.1</archetype.version>
+        <archetype.version>3.1.2</archetype.version>
     </properties>
 
     <build>
diff --git a/assemblies/features/base/pom.xml b/assemblies/features/base/pom.xml
index 14615d6..701cbcf 100644
--- a/assemblies/features/base/pom.xml
+++ b/assemblies/features/base/pom.xml
@@ -37,32 +37,32 @@
     </properties>
 
     <dependencies>
-                    <dependency>
-                        <groupId>jakarta.xml.bind</groupId>
-                        <artifactId>jakarta.xml.bind-api</artifactId>
-                    </dependency>
-                    <dependency>
-                        <groupId>javax.annotation</groupId>
-                        <artifactId>javax.annotation-api</artifactId>
-                        <version>${javax.annotation.version}</version>
-                    </dependency>
-                    <dependency>
-                        <groupId>com.sun.activation</groupId>
-                        <artifactId>javax.activation</artifactId>
-                        <version>1.2.0</version>
-                    </dependency>
-                    <dependency>
-                        <groupId>org.glassfish.jaxb</groupId>
-                        <artifactId>jaxb-runtime</artifactId>
-                    </dependency>
-                    <dependency>
-                        <groupId>org.glassfish.jaxb</groupId>
-                        <artifactId>txw2</artifactId>
-                    </dependency>
-                    <dependency>
-                        <groupId>com.sun.istack</groupId>
-                        <artifactId>istack-commons-runtime</artifactId>
-                    </dependency>
+        <dependency>
+            <groupId>jakarta.xml.bind</groupId>
+            <artifactId>jakarta.xml.bind-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>javax.annotation</groupId>
+            <artifactId>javax.annotation-api</artifactId>
+            <version>${javax.annotation.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.activation</groupId>
+            <artifactId>javax.activation</artifactId>
+            <version>1.2.0</version>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>jaxb-runtime</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.jaxb</groupId>
+            <artifactId>txw2</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.sun.istack</groupId>
+            <artifactId>istack-commons-runtime</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.apache.karaf.specs</groupId>
             <artifactId>org.apache.karaf.specs.locator</artifactId>
@@ -108,7 +108,12 @@
             <artifactId>org.apache.felix.framework</artifactId>
             <scope>runtime</scope>
         </dependency>
-
+        <dependency>
+            <groupId>org.glassfish.external</groupId>
+            <artifactId>opendmk_jmxremote_optional_jar</artifactId>
+            <version>1.0-b01-ea</version>
+            <scope>runtime</scope>
+        </dependency>
     </dependencies>
 
     <build>
@@ -195,14 +200,14 @@
                                     <artifactId>jaxb-runtime</artifactId>
                                     <outputDirectory>target/classes/resources/lib/jdk9plus</outputDirectory>
                                 </artifactItem>
- 				<artifactItem>
+                                <artifactItem>
                                     <groupId>org.glassfish.jaxb</groupId>
                                     <artifactId>txw2</artifactId>
                                     <outputDirectory>target/classes/resources/lib/jdk9plus</outputDirectory>
                                 </artifactItem>
                                 <artifactItem>
-     				    <groupId>com.sun.istack</groupId>
-                        	    <artifactId>istack-commons-runtime</artifactId>
+                                    <groupId>com.sun.istack</groupId>
+                                    <artifactId>istack-commons-runtime</artifactId>
                                     <outputDirectory>target/classes/resources/lib/jdk9plus</outputDirectory>
                                 </artifactItem>
                                 <artifactItem>
@@ -221,6 +226,11 @@
                                     <outputDirectory>target/classes/resources/lib/boot</outputDirectory>
                                 </artifactItem>
                                 <artifactItem>
+                                    <groupId>org.glassfish.external</groupId>
+                                    <artifactId>opendmk_jmxremote_optional_jar</artifactId>
+                                    <outputDirectory>target/classes/resources/lib/boot</outputDirectory>
+                                </artifactItem>
+                                <artifactItem>
                                     <groupId>org.apache.karaf</groupId>
                                     <artifactId>org.apache.karaf.client</artifactId>
                                     <outputDirectory>target/classes/resources/system/org/apache/karaf/org.apache.karaf.client/${project.version}</outputDirectory>
@@ -229,7 +239,8 @@
                                     <groupId>org.apache.felix</groupId>
                                     <artifactId>org.apache.felix.framework</artifactId>
                                     <outputDirectory>target/classes/resources/system/org/apache/felix/org.apache.felix.framework/${felix.framework.version}</outputDirectory>
-                                    <destFileName>org.apache.felix.framework-${felix.framework.version}.jar</destFileName>
+                                    <destFileName>org.apache.felix.framework-${felix.framework.version}.jar
+                                    </destFileName>
                                 </artifactItem>
                                 <artifactItem>
                                     <groupId>${equinox.groupId}</groupId>
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties b/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties
index fe30b1a..d987fc9 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties
+++ b/assemblies/features/base/src/main/filtered-resources/resources/etc/config.properties
@@ -89,7 +89,9 @@
 #
 org.osgi.framework.system.packages.extra= \
  org.apache.karaf.branding, \
- sun.misc
+ sun.misc, \
+ com.sun.jmx.remote.protocol, \
+ com.sun.jmx.remote.protocol.jmxmp
 
 org.osgi.framework.system.capabilities= \
  ${eecap-${java.specification.version}}, \
diff --git a/assemblies/features/framework/pom.xml b/assemblies/features/framework/pom.xml
index a89663e..6bc683d 100644
--- a/assemblies/features/framework/pom.xml
+++ b/assemblies/features/framework/pom.xml
@@ -74,6 +74,12 @@
             <artifactId>org.apache.felix.framework</artifactId>
             <scope>runtime</scope>
         </dependency>
+        <dependency>
+            <groupId>org.glassfish.external</groupId>
+            <artifactId>opendmk_jmxremote_optional_jar</artifactId>
+            <version>1.0-b01-ea</version>
+            <scope>runtime</scope>
+        </dependency>
 
         <!-- Client deps -->
         <dependency>
diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml
index 16e1ac8..2a59eca 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -1255,12 +1255,12 @@
 rmiRegistryHost = 127.0.0.1
 
 #
-# Port number for RMI server connection
+# Port number for RMI connector server connection
 #
 rmiServerPort = 44444
 
 #
-# Host for RMI server
+# Host for RMI connector server
 #
 rmiServerHost = 127.0.0.1
 
@@ -1270,11 +1270,31 @@
 jmxRealm = karaf
 
 #
-# The service URL for the JMXConnectorServer
+# The service URL for the JMX RMI connector
 #
 serviceUrl = service:jmx:rmi://${rmiServerHost}:${rmiServerPort}/jndi/rmi://${rmiRegistryHost}:${rmiRegistryPort}/karaf-${karaf.name}
 
 #
+# JMXMP connector enabled
+#
+jmxmpEnabled = false
+
+#
+# JMXMP connector host name
+#
+jmxmpHost = 127.0.0.1
+
+#
+# JMXMP connector port number
+#
+jmxmpPort = 9999
+
+#
+# JMXMP connector service URL
+#
+jmxmpServiceUrl = service:jmx:jmxmp://${jmxmpHost}:${jmxmpPort}
+
+#
 # Whether any threads started for the JMXConnectorServer should be started as daemon threads
 #
 daemon = true
@@ -1285,11 +1305,16 @@
 threaded = true
 
 #
-# The ObjectName used to register the JMXConnectorServer
+# The ObjectName used to register the JMX RMI connector
 #
 objectName = connector:name=rmi
 
 #
+# The ObjectName used to register the JMXMP connector
+#
+jmxmpObjectName = connector:name=jmxmp
+
+#
 # Timeout to lookup for the keystore in case of SSL authentication usage
 #
 #keyStoreAvailabilityTimeout = 5000
diff --git a/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java b/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java
index 129d89a..32c9cf1 100644
--- a/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java
+++ b/features/extension/src/main/java/org/apache/karaf/features/extension/BundleWires.java
@@ -28,7 +28,10 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
@@ -42,14 +45,16 @@
 
 class BundleWires {
     long bundleId;
-    Map<String, String> wiring = new HashMap<>();
+    Map<String, Set<String>> wiring = new HashMap<>();
 
     BundleWires(Bundle bundle) {
         this.bundleId = bundle.getBundleId();
         for (BundleWire wire : bundle.adapt(BundleWiring.class).getRequiredWires(null)) {
             String requirementId = getRequirementId(wire.getRequirement());
             String capabilityId = getCapabilityId(wire.getCapability());
-            this.wiring.put(requirementId, capabilityId);
+
+            Set<String> capabilityIds = this.wiring.computeIfAbsent( requirementId, key -> new HashSet<>() );
+            capabilityIds.add(capabilityId);
         }
     }
 
@@ -59,7 +64,8 @@
             String key = reader.readLine();
             String val = reader.readLine();
             if (key != null && val != null) {
-                this.wiring.put(key, val);
+                Set<String> capabilityIds = this.wiring.computeIfAbsent( key, k -> new HashSet<>() );
+                capabilityIds.add(val);
             } else {
                 break;
             }
@@ -72,9 +78,14 @@
             Path file = path.resolve(Long.toString(this.bundleId));
             Files.createDirectories(file.getParent());
             try (BufferedWriter fw = Files.newBufferedWriter(file, TRUNCATE_EXISTING, WRITE, CREATE)) {
-                for (Map.Entry<String, String> wire : wiring.entrySet()) {
-                    fw.append(wire.getKey()).append('\n');
-                    fw.append(wire.getValue()).append('\n');
+                for (Map.Entry<String, Set<String>> wires : wiring.entrySet()) {
+                    String requirementId = wires.getKey();
+                    Set<String> capabilityIds = wires.getValue();
+
+                    for ( String capabilityId : capabilityIds ) {
+                        fw.append( requirementId ).append( '\n' );
+                        fw.append( capabilityId ).append( '\n' );
+                    }
                 }
             }
         } catch (IOException e) {
@@ -92,13 +103,12 @@
         }
     }
 
-    long getFragmentHost() {
+    long[] getFragmentHosts() {
         return wiring.entrySet().stream() //
             .filter(e -> e.getKey().startsWith(HostNamespace.HOST_NAMESPACE)) //
             .map(Map.Entry::getValue) //
-            .mapToLong(this::getBundleId) //
-            .findFirst() //
-            .orElse(-1);
+            .flatMap( Set::stream ) //
+            .mapToLong(this::getBundleId).toArray();
     }
     
     private long getBundleId(String value) {
@@ -109,14 +119,20 @@
         return Long.parseLong(value.trim());
     }
 
-    void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
-        String cap = wiring.get(getRequirementId(requirement));
-        candidates.removeIf(cand -> checkRemove(cap, cand));
+    Set<BundleCapability> filterCandidates( BundleRequirement requirement, Collection<BundleCapability> candidates) {
+        Set<String> wiredCapabilityIds = wiring.get(getRequirementId(requirement));
+
+        return candidates.stream() //
+            .filter( capability -> isCapabilityWiredToBundle( wiredCapabilityIds, capability ) ) //
+            .collect( Collectors.toSet() );
     }
 
-    private boolean checkRemove(String cap, BundleCapability cand) {
-        return cap != null && !cap.equals(getCapabilityId(cand))
-            || cap == null && cand.getRevision().getBundle().getBundleId() != this.bundleId;
+    private boolean isCapabilityWiredToBundle( Set<String> capabilityIds, BundleCapability capability) {
+        return
+            // is this bundle wired to the candidate capability?
+            (capabilityIds != null && capabilityIds.contains(getCapabilityId(capability)))
+            // if not and the bundle has no wirings to the capability check if itself satisfies it
+            || (capabilityIds == null && capability.getRevision().getBundle().getBundleId() == this.bundleId);
     }
 
     private String getRequirementId(Requirement requirement) {
diff --git a/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java b/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java
index 06b8bc6..976ee17 100644
--- a/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java
+++ b/features/extension/src/main/java/org/apache/karaf/features/extension/StoredWiringResolver.java
@@ -23,7 +23,9 @@
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
+import java.util.Set;
 
 import org.osgi.framework.Bundle;
 import org.osgi.framework.hooks.resolver.ResolverHook;
@@ -37,7 +39,7 @@
 import org.osgi.resource.Resource;
 
 class StoredWiringResolver implements ResolverHook {
-    private final Map<Long, BundleWires> wiring = new HashMap<>();
+    final Map<Long, BundleWires> wiring = new HashMap<>();
     private Path path;
 
     StoredWiringResolver(Path path) {
@@ -75,22 +77,43 @@
 
     @Override
     public void filterMatches(BundleRequirement requirement, Collection<BundleCapability> candidates) {
-        long sourceId = getBundleId(requirement);
-        wiring.get(sourceId).filterMatches(requirement, candidates);
+        long[] requirementSourceBundleIds = getRequirementBundleIds(requirement);
+
+        Set<BundleCapability> goodCapabilityCandidates = new HashSet<>();
+        for ( long requirementBundleId : requirementSourceBundleIds ) {
+            BundleWires bundleWires = wiring.get(requirementBundleId);
+
+            goodCapabilityCandidates.addAll( bundleWires.filterCandidates( requirement, candidates ) );
+        }
+
+        candidates.retainAll( goodCapabilityCandidates );
     }
 
     @Override
     public void end() {
     }
 
-    private long getBundleId(BundleRequirement requirement) {
+    /**
+     * Retrieves the id of the bundle declaring the requirement.
+     *
+     * In the case of fragment bundles the wiring is made from each Host bundle it is attached,
+     * except the osgi.wiring.host requirement itself.
+     *
+     * @param requirement the requirement being resolved
+     *
+     * @return the ids of the bundles owning the requirement
+     */
+    private long[] getRequirementBundleIds(BundleRequirement requirement) {
         long sourceId = requirement.getRevision().getBundle().getBundleId();
+
         if (isFragment(requirement.getRevision())
             && !requirement.getNamespace().equals(HostNamespace.HOST_NAMESPACE)
             && !requirement.getNamespace().equals(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE)) {
-            sourceId = wiring.get(sourceId).getFragmentHost();
+
+            return wiring.get(sourceId).getFragmentHosts();
         }
-        return sourceId;
+
+        return new long[] { sourceId };
     }
 
     private static boolean isFragment(Resource resource) {
@@ -111,7 +134,7 @@
 
     synchronized void delete(Bundle bundle) {
         if (wiring.get(bundle.getBundleId()) != null) {
-            wiring.get(bundle.getBundleId()).delete(path);
+            wiring.remove(bundle.getBundleId()).delete(path);
         }
     }
 }
diff --git a/features/extension/src/test/java/org/apache/karaf/features/extension/BundleWiresTest.java b/features/extension/src/test/java/org/apache/karaf/features/extension/BundleWiresTest.java
index d631ca7..b8bf7cd3 100644
--- a/features/extension/src/test/java/org/apache/karaf/features/extension/BundleWiresTest.java
+++ b/features/extension/src/test/java/org/apache/karaf/features/extension/BundleWiresTest.java
@@ -25,13 +25,10 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import java.util.stream.LongStream;
 
 import org.easymock.EasyMock;
 import org.easymock.IMocksControl;
@@ -40,6 +37,7 @@
 import org.junit.Test;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.Constants;
+import org.osgi.framework.namespace.HostNamespace;
 import org.osgi.framework.namespace.PackageNamespace;
 import org.osgi.framework.wiring.BundleCapability;
 import org.osgi.framework.wiring.BundleRequirement;
@@ -63,8 +61,8 @@
 
     @Test
     public void testFromBundle() throws IOException {
-        BundleWire wire = packageWire(packageFilter, bundleCap(targetBundleId, targetBundleVersion));
-        Bundle bundle = wiredBundle(Collections.singletonList(wire));
+        BundleWire wire = packageWire(packageFilter, bundleCap(targetBundleId, targetBundleVersion, true ));
+        Bundle bundle = wiredBundle(Arrays.asList(wire));
         c.replay();
         BundleWires bwires = new BundleWires(bundle);
         bwires.save(BASE_PATH);
@@ -79,25 +77,101 @@
     public void testFromFile() throws IOException {
         BundleWires wires = readFromFile();
         assertEquals(1, wires.wiring.size());
-        Entry<String, String> wire = wires.wiring.entrySet().iterator().next();
+        Entry<String, Set<String>> wire = wires.wiring.entrySet().iterator().next();
         assertEquals(PackageNamespace.PACKAGE_NAMESPACE + "; " + packageFilter, wire.getKey());
-        assertEquals(targetBundleId + "; version=" + targetBundleVersion, wire.getValue());
+        assertEquals(targetBundleId + "; version=" + targetBundleVersion, wire.getValue().iterator().next());
     }
     
     @Test
-    public void testFilterMatches() throws IOException {
+    public void testFilterCandidates() throws IOException {
         BundleWires wires = readFromFile();
         BundleRequirement req = packageRequirement(packageFilter);
-        BundleCapability candidate1 = bundleCap(targetBundleId, targetBundleVersion);
+        BundleCapability candidate1 = bundleCap(targetBundleId, targetBundleVersion, true );
         List<BundleCapability> candidates = new ArrayList<>();
         candidates.add(candidate1);
-        BundleCapability matchingCandidate = bundleCap(targetBundleId, "1.1.0");
+        BundleCapability matchingCandidate = bundleCap(targetBundleId, "1.1.0", true );
         candidates.add(matchingCandidate);
         c.replay();
-        
-        wires.filterMatches(req, candidates);
-        assertEquals(1, candidates.size());
-        assertEquals(candidate1, candidates.iterator().next());
+
+        Set<BundleCapability> goodCandidates = wires.filterCandidates( req, candidates );
+        assertEquals(1, goodCandidates.size());
+        assertEquals(candidate1, goodCandidates.iterator().next());
+        c.verify();
+    }
+
+    @Test
+    public void testFragmentWithThreeHosts() throws IOException {
+        String hostFilter = "(&(osgi.wiring.host=our-host)(bundle-version>=0.0.0))";
+
+        long host1BundleId = 2L;
+        String host1Version = "1.0.0";
+        long host2BundleId = 3L;
+        String host2Version = "1.5.0";
+        long host3BundleId = 4L;
+        String host3Version = "2.0.0";
+
+        BundleWire host1 = hostWire(hostFilter, bundleCap(host1BundleId, host1Version, true ));
+        BundleWire host2 = hostWire(hostFilter, bundleCap(host2BundleId, host2Version, true ));
+        BundleWire host3 = hostWire(hostFilter, bundleCap(host3BundleId, host3Version, true ));
+
+        Bundle bundle = wiredBundle(Arrays.asList(host1, host2, host3));
+
+        c.replay();
+
+        BundleWires bwires = new BundleWires(bundle);
+        bwires.save(BASE_PATH);
+
+        c.verify();
+
+        List<Long> hosts = LongStream.of( bwires.getFragmentHosts() ).boxed().collect( Collectors.toList());
+
+        Assert.assertTrue(hosts.contains( host1BundleId ));
+        Assert.assertTrue(hosts.contains( host2BundleId ));
+        Assert.assertTrue(hosts.contains( host3BundleId ));
+
+        Iterator<String> lines = Files.lines(new File("target/bundles/1").toPath()).iterator();
+
+        // the save order isn't guaranteed (a Set is used internally), so we need to collect the info first
+        Set<String> wirings = new HashSet<>();
+
+        Assert.assertEquals(HostNamespace.HOST_NAMESPACE + "; " + hostFilter, lines.next());
+        wirings.add( lines.next() );
+
+        Assert.assertEquals(HostNamespace.HOST_NAMESPACE + "; " + hostFilter, lines.next());
+        wirings.add( lines.next() );
+
+        Assert.assertEquals(HostNamespace.HOST_NAMESPACE + "; " + hostFilter, lines.next());
+        wirings.add( lines.next() );
+
+        Assert.assertTrue(wirings.contains( host1BundleId + "; version=" + host1Version ));
+        Assert.assertTrue(wirings.contains( host2BundleId + "; version=" + host2Version ));
+        Assert.assertTrue(wirings.contains( host3BundleId + "; version=" + host3Version ));
+
+        bwires.delete(BASE_PATH);
+    }
+
+    @Test
+    public void testFilterCandidatesSelfSatisfiedCapability() throws IOException {
+        // no wires are created with itself
+        Bundle bundle = wiredBundle( Collections.emptyList() );
+
+        BundleCapability candidate1 = bundleCap(1, targetBundleVersion, false );
+        List<BundleCapability> candidates = new ArrayList<>();
+        candidates.add(candidate1);
+
+        BundleCapability matchingCandidate = bundleCap(2, "1.1.0", false );
+        candidates.add(matchingCandidate);
+
+        BundleRequirement req = packageRequirement(packageFilter);
+
+        c.replay();
+
+        BundleWires wires = new BundleWires(bundle);
+
+        Set<BundleCapability> goodCandidates = wires.filterCandidates( req, candidates );
+        assertEquals(1, goodCandidates.size());
+        assertEquals(candidate1, goodCandidates.iterator().next());
+
         c.verify();
     }
 
@@ -125,16 +199,35 @@
         return req;
     }
 
-    private BundleCapability bundleCap(long bundleId, String version) {
+    private BundleWire hostWire(String hostFilter, BundleCapability bundleRef) {
+        BundleWire wire = c.createMock(BundleWire.class);
+        BundleRequirement req = hostRequirement(hostFilter);
+        expect(wire.getRequirement()).andReturn(req);
+        expect(wire.getCapability()).andReturn(bundleRef);
+        return wire;
+    }
+
+    private BundleRequirement hostRequirement(String packageFilter) {
+        BundleRequirement req = c.createMock(BundleRequirement.class);
+        Map<String, String> directives = new HashMap<>();
+        directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, packageFilter);
+        expect(req.getDirectives()).andReturn(directives);
+        expect(req.getNamespace()).andReturn(HostNamespace.HOST_NAMESPACE);
+        return req;
+    }
+
+    private BundleCapability bundleCap( long bundleId, String version, boolean expectGetAttributes ) {
         BundleRevision rev = c.createMock(BundleRevision.class);
         Bundle bundle = c.createMock(Bundle.class);
         expect(bundle.getBundleId()).andReturn(bundleId);
         expect(rev.getBundle()).andReturn(bundle);
         BundleCapability cap = c.createMock(BundleCapability.class);
         expect(cap.getRevision()).andReturn(rev);
-        Map<String, Object> attrs = new HashMap<>();
-        attrs.put(Constants.VERSION_ATTRIBUTE, version);
-        expect(cap.getAttributes()).andReturn(attrs);
+        if(expectGetAttributes) {
+            Map<String, Object> attrs = new HashMap<>();
+            attrs.put( Constants.VERSION_ATTRIBUTE, version );
+            expect( cap.getAttributes() ).andReturn( attrs );
+        }
         return cap;
     }
 
diff --git a/features/extension/src/test/java/org/apache/karaf/features/extension/StoredWiringResolverTest.java b/features/extension/src/test/java/org/apache/karaf/features/extension/StoredWiringResolverTest.java
new file mode 100644
index 0000000..ce5b82d
--- /dev/null
+++ b/features/extension/src/test/java/org/apache/karaf/features/extension/StoredWiringResolverTest.java
@@ -0,0 +1,311 @@
+/*
+ * 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.extension;
+
+import org.easymock.EasyMock;
+import org.easymock.IMocksControl;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.Constants;
+import org.osgi.framework.namespace.IdentityNamespace;
+import org.osgi.framework.namespace.PackageNamespace;
+import org.osgi.framework.wiring.*;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Namespace;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+
+public class StoredWiringResolverTest {
+    private static final String TEST_RESOURCES_WIRINGS = "target/test-classes/wirings/";
+
+    private static final int EXPECTED_STOCK_WIRINGS = 3;
+    private static final String PACKAGE_FILTER = "(&(osgi.wiring.package=org.osgi.framework)(version>=1.6.0)(!(version>=2.0.0)))";
+
+    private IMocksControl c;
+
+    private StoredWiringResolver wiringResolver;
+
+    @Before
+    public void init() {
+        c = EasyMock.createControl();
+
+        wiringResolver = new StoredWiringResolver(new File(TEST_RESOURCES_WIRINGS).toPath());
+        wiringResolver.load();
+    }
+
+    @After
+    public void cleanup() {
+        new File(TEST_RESOURCES_WIRINGS + "25").delete();
+    }
+
+    @Test
+    public void load() {
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+
+        Assert.assertTrue(wiringResolver.wiring.containsKey(1L));
+        Assert.assertTrue(wiringResolver.wiring.containsKey(5L));
+        Assert.assertTrue(wiringResolver.wiring.containsKey(9L));
+    }
+
+    @Test
+    public void filterMatches() {
+        List<BundleCapability> candidates = new ArrayList<>();
+
+        long sourceBundleId = 1L;
+        long targetBundleId = 2L;
+        String targetBundleVersion = "1.0.1";
+
+        BundleCapability candidate1 = mockBundleCapability(targetBundleId, targetBundleVersion);
+        candidates.add(candidate1);
+
+        BundleCapability candidate2 = mockBundleCapability(targetBundleId, "2.1.0");
+        candidates.add(candidate2);
+
+        BundleRequirement req = packageRequirement(sourceBundleId, false);
+
+        c.replay();
+
+        wiringResolver.filterMatches(req, candidates);
+
+        c.verify();
+
+        assertEquals(1, candidates.size());
+        assertEquals(candidate1, candidates.iterator().next());
+    }
+
+    @Test
+    public void filterMatchesFragmentBundle() {
+        List<BundleCapability> candidates = new ArrayList<>();
+
+        long sourceBundleId = 5L;
+        long targetBundleId = 2L;
+        String targetBundleVersion = "1.0.1";
+
+        BundleCapability candidate1 = mockBundleCapability(targetBundleId, targetBundleVersion);
+        candidates.add(candidate1);
+
+        BundleCapability candidate2 = mockBundleCapability(targetBundleId, "2.1.0");
+        candidates.add(candidate2);
+
+        BundleRequirement req = packageRequirement(sourceBundleId, true);
+
+        c.replay();
+
+        wiringResolver.filterMatches(req, candidates);
+
+        c.verify();
+
+        assertEquals(1, candidates.size());
+        assertEquals(candidate1, candidates.iterator().next());
+    }
+
+    @Test
+    public void updateNew() {
+        long newBundleId = 25L;
+        File file = new File(TEST_RESOURCES_WIRINGS + newBundleId);
+
+        Bundle bundle = wiredMockBundle(newBundleId, Collections.emptyList() );
+
+        c.replay();
+
+        // preconditions
+        Assert.assertFalse(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertFalse(wiringResolver.wiring.containsKey(newBundleId));
+
+        wiringResolver.update(bundle);
+
+        c.verify();
+
+        // assertions
+        Assert.assertTrue(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS + 1, wiringResolver.wiring.size());
+        Assert.assertTrue(wiringResolver.wiring.containsKey(newBundleId));
+    }
+
+    @Test
+    public void updateExisting() {
+        long newBundleId = 9L;
+        File file = new File(TEST_RESOURCES_WIRINGS + newBundleId);
+
+        BundleWire wire = mockBundleWire(PackageNamespace.PACKAGE_NAMESPACE, PACKAGE_FILTER,
+                mockBundleCapability(25L, "1.7.8"));
+        Bundle bundle = wiredMockBundle(newBundleId, Collections.singletonList(wire));
+
+        c.replay();
+
+        // preconditions
+        Assert.assertTrue(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertTrue(wiringResolver.wiring.containsKey(newBundleId));
+
+        wiringResolver.update(bundle);
+
+        c.verify();
+
+        // assertions
+        Assert.assertTrue(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertTrue(wiringResolver.wiring.containsKey(newBundleId));
+    }
+
+    @Test
+    public void delete() {
+        long newBundleId = 25L;
+        File file = new File(TEST_RESOURCES_WIRINGS + newBundleId);
+
+        Bundle bundle = wiredMockBundle(newBundleId, Collections.emptyList() );
+
+        c.replay();
+
+        wiringResolver.update(bundle);
+
+        // preconditions
+        Assert.assertTrue(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS + 1, wiringResolver.wiring.size());
+        Assert.assertTrue(wiringResolver.wiring.containsKey(newBundleId));
+
+        wiringResolver.delete(bundle);
+
+        c.verify();
+
+        // assertions
+        Assert.assertFalse(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertFalse(wiringResolver.wiring.containsKey(newBundleId));
+    }
+
+    @Test
+    public void deleteNonExisting() {
+        long otherBundleId = 30L;
+        File file = new File(TEST_RESOURCES_WIRINGS + otherBundleId);
+
+        Bundle bundle = mockBundle(otherBundleId);
+
+        c.replay();
+
+        // preconditions
+        Assert.assertFalse(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertFalse(wiringResolver.wiring.containsKey(otherBundleId));
+
+        wiringResolver.delete(bundle);
+
+        c.verify();
+
+        // assertions
+        Assert.assertFalse(file.exists());
+        Assert.assertEquals(EXPECTED_STOCK_WIRINGS, wiringResolver.wiring.size());
+        Assert.assertFalse(wiringResolver.wiring.containsKey(otherBundleId));
+    }
+
+    private Bundle mockBundle(long bundleId) {
+        Bundle bundle = c.createMock(Bundle.class);
+        expect(bundle.getBundleId()).andReturn(bundleId).atLeastOnce();
+
+        return bundle;
+    }
+
+    private Bundle wiredMockBundle(long bundleId, List<BundleWire> wires) {
+        Bundle bundle = mockBundle(bundleId);
+
+        BundleWiring wiring = c.createMock(BundleWiring.class);
+        expect(wiring.getRequiredWires(null)).andReturn(wires);
+        expect(bundle.adapt(BundleWiring.class)).andReturn(wiring);
+
+        return bundle;
+    }
+
+    private BundleWire mockBundleWire(String packageNamespace, String packageFilter, BundleCapability capability) {
+        BundleWire wire = c.createMock(BundleWire.class);
+
+        BundleRequirement req = c.createMock(BundleRequirement.class);
+        expect(req.getNamespace()).andReturn(packageNamespace);
+
+        Map<String, String> directives = new HashMap<>();
+        directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, packageFilter);
+        expect(req.getDirectives()).andReturn(directives);
+
+        expect(wire.getRequirement()).andReturn(req);
+        expect(wire.getCapability()).andReturn(capability);
+
+        return wire;
+    }
+
+    private BundleRequirement packageRequirement(long sourceBundleId, boolean isFragment) {
+        Bundle bundle = c.createMock(Bundle.class);
+        expect(bundle.getBundleId()).andReturn(sourceBundleId).atLeastOnce();
+
+        BundleRevision rev = c.createMock(BundleRevision.class);
+        expect(rev.getBundle()).andReturn(bundle);
+
+        List<Capability> bundleCapabilities = new ArrayList<>();
+
+        if(isFragment) {
+            BundleCapability cap = c.createMock(BundleCapability.class);
+
+            expect(cap.getNamespace()).andReturn(IdentityNamespace.IDENTITY_NAMESPACE);
+
+            Map<String, Object> attrs = new HashMap<>();
+            attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, IdentityNamespace.TYPE_FRAGMENT);
+            expect(cap.getAttributes()).andReturn(attrs);
+
+            bundleCapabilities.add(cap);
+        }
+
+        expect(rev.getCapabilities(null)).andReturn(bundleCapabilities);
+
+        BundleRequirement req = c.createMock(BundleRequirement.class);
+
+        Map<String, String> directives = new HashMap<>();
+        directives.put(Namespace.REQUIREMENT_FILTER_DIRECTIVE, PACKAGE_FILTER);
+        expect(req.getDirectives()).andReturn(directives);
+
+        expect(req.getNamespace()).andReturn(PackageNamespace.PACKAGE_NAMESPACE).atLeastOnce();
+
+        expect(req.getRevision()).andReturn(rev).atLeastOnce();
+
+        return req;
+    }
+
+    private BundleCapability mockBundleCapability(long bundleId, String version) {
+        Bundle bundle = mockBundle(bundleId);
+
+        BundleRevision rev = c.createMock(BundleRevision.class);
+        expect(rev.getBundle()).andReturn(bundle);
+
+        BundleCapability cap = c.createMock(BundleCapability.class);
+        expect(cap.getRevision()).andReturn(rev);
+
+        Map<String, Object> attrs = new HashMap<>();
+        attrs.put( Constants.VERSION_ATTRIBUTE, version );
+        expect( cap.getAttributes() ).andReturn( attrs );
+
+        return cap;
+    }
+}
\ No newline at end of file
diff --git a/features/extension/src/test/resources/wirings/5 b/features/extension/src/test/resources/wirings/5
new file mode 100644
index 0000000..cd5b3f7
--- /dev/null
+++ b/features/extension/src/test/resources/wirings/5
@@ -0,0 +1,4 @@
+osgi.ee; (&(osgi.ee=JavaSE)(version=1.8))
+0; version=[1.0.0, 1.1.0, 1.2.0, 1.3.0, 1.4.0, 1.5.0, 1.6.0, 1.7.0, 1.8.0]
+osgi.wiring.host; (&(osgi.wiring.host=our-host)(bundle-version>=0.0.0))
+1
diff --git a/features/extension/src/test/resources/wirings/9 b/features/extension/src/test/resources/wirings/9
new file mode 100644
index 0000000..4b7f86f
--- /dev/null
+++ b/features/extension/src/test/resources/wirings/9
@@ -0,0 +1,2 @@
+osgi.wiring.package; (&(osgi.wiring.package=org.osgi.framework)(version>=1.6.0)(!(version>=2.0.0)))
+25; version=1.7.8
diff --git a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties
index 0495f45..d8b1f95 100644
--- a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties
+++ b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/config.properties
@@ -90,6 +90,8 @@
 org.osgi.framework.system.packages.extra = \
     org.apache.karaf.branding, \
     sun.misc, \
+    com.sun.jmx.remote.protocol, \
+    com.sun.jmx.remote.protocol.jmxmp, \
     org.apache.karaf.diagnostic.core;uses:=org.osgi.framework;version=@@karaf.osgi.version@@, \
     org.apache.karaf.diagnostic.core.common;uses:=org.apache.karaf.diagnostic.core;version=@@karaf.osgi.version@@, \
     org.apache.karaf.jaas.boot;uses:=\"javax.security.auth,javax.security.auth.callback,javax.security.auth.login,javax.security.auth.spi,org.osgi.framework\";version=@@karaf.osgi.version@@, \
diff --git a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/org.apache.karaf.management.cfg b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/org.apache.karaf.management.cfg
index 643739b..d0a9ccc 100644
--- a/instance/src/main/resources/org/apache/karaf/instance/resources/etc/org.apache.karaf.management.cfg
+++ b/instance/src/main/resources/org/apache/karaf/instance/resources/etc/org.apache.karaf.management.cfg
@@ -51,6 +51,26 @@
 serviceUrl = service:jmx:rmi://${rmiServerHost}:${rmiServerPort}/jndi/rmi://${rmiRegistryHost}:${rmiRegistryPort}/karaf-${karaf.name}
 
 #
+# JMXMP connector enabled
+#
+jmxmpEnabled = false
+
+#
+# JMXMP connector host name
+#
+jmxmpHost = 127.0.0.1
+
+#
+# JMXMP connector port number
+#
+jmxmpPort = 9999
+
+#
+# JMXMP connector service URL
+#
+jmxmpServiceUrl = service:jmx:jmxmp://${jmxmpHost}:${jmxmpPort}
+
+#
 # Whether any threads started for the JMXConnectorServer should be started as daemon threads
 #
 daemon = true
@@ -66,6 +86,11 @@
 objectName = connector:name=rmi
 
 #
+# The ObjectName used to register the JMXMP connector
+#
+jmxmpObjectName = connector:name=jmxmp
+
+#
 # Timeout to lookup for the keystore in case of SSL authentication usage
 #
 #keyStoreAvailabilityTimeout = 5000
diff --git a/management/server/pom.xml b/management/server/pom.xml
index 5c76cd0..64d4e2b 100644
--- a/management/server/pom.xml
+++ b/management/server/pom.xml
@@ -69,6 +69,18 @@
             <artifactId>org.apache.karaf.util</artifactId>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.glassfish.external</groupId>
+            <artifactId>opendmk_jmxremote_optional_jar</artifactId>
+            <version>1.0-b01-ea</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.glassfish.extras</groupId>
+            <artifactId>bean-validator</artifactId>
+            <version>4.0.0.Alpha3</version>
+            <scope>provided</scope>
+        </dependency>
 
         <dependency>
             <groupId>org.slf4j</groupId>
@@ -114,6 +126,8 @@
                         </Export-Package>
                         <Import-Package>
                             org.osgi.service.event*;resolution:=optional,
+                            com.sun.jmx.remote.protocol;resolution:=optional,
+                            com.sun.jdmk.security.sasl;resolution:=optional,
                             *
                         </Import-Package>
                         <Private-Package>
diff --git a/management/server/src/main/java/org/apache/karaf/management/ConnectorServerFactory.java b/management/server/src/main/java/org/apache/karaf/management/ConnectorServerFactory.java
index 73fcb6b..204c3b5 100644
--- a/management/server/src/main/java/org/apache/karaf/management/ConnectorServerFactory.java
+++ b/management/server/src/main/java/org/apache/karaf/management/ConnectorServerFactory.java
@@ -56,12 +56,17 @@
     private MBeanServer server;
     private KarafMBeanServerGuard guard;
     private String serviceUrl;
+    private boolean jmxmpEnabled;
+    private String jmxmpServiceUrl;
     private String rmiServerHost;
     private Map<String, Object> environment;
+    private Map<String, Object> jmxmpEnvironment;
     private ObjectName objectName;
+    private ObjectName jmxmpObjectName;
     private boolean threaded = false;
     private boolean daemon = false;
     private JMXConnectorServer connectorServer;
+    private JMXConnectorServer jmxmpConnectorServer;
 
     private long keyStoreAvailabilityTimeout = 5000;
     private AuthenticatorType authenticatorType = AuthenticatorType.PASSWORD;
@@ -98,6 +103,22 @@
         this.serviceUrl = serviceUrl;
     }
 
+    public boolean isJmxmpEnabled() {
+        return this.jmxmpEnabled;
+    }
+
+    public void setJmxmpEnabled(boolean jmxmpEnabled) {
+        this.jmxmpEnabled = jmxmpEnabled;
+    }
+
+    public String getJmxmpServiceUrl() {
+        return jmxmpServiceUrl;
+    }
+
+    public void setJmxmpServiceUrl(String jmxmpServiceUrl) {
+        this.jmxmpServiceUrl = jmxmpServiceUrl;
+    }
+
     public String getRmiServerHost() {
         return this.rmiServerHost;
     }
@@ -114,6 +135,14 @@
         this.environment = environment;
     }
 
+    public Map<String, Object> getJmxmpEnvironment() {
+        return this.jmxmpEnvironment;
+    }
+
+    public void setJmxmpEnvironment(Map<String,Object> jmxmpEnvironment) {
+        this.jmxmpEnvironment = jmxmpEnvironment;
+    }
+
     public ObjectName getObjectName() {
         return objectName;
     }
@@ -122,6 +151,14 @@
         this.objectName = objectName;
     }
 
+    public ObjectName getJmxmpObjectName() {
+        return this.jmxmpObjectName;
+    }
+
+    public void setJmxmpObjectName(ObjectName jmxmpObjectName) {
+        this.jmxmpObjectName = jmxmpObjectName;
+    }
+
     public boolean isThreaded() {
         return threaded;
     }
@@ -259,12 +296,23 @@
             this.server.registerMBean(this.connectorServer, this.objectName);
         }
 
+        if (jmxmpEnabled) {
+            JMXServiceURL jmxmpUrl = new JMXServiceURL(this.jmxmpServiceUrl);
+            this.jmxmpConnectorServer = JMXConnectorServerFactory.newJMXConnectorServer(jmxmpUrl, this.jmxmpEnvironment, guardedServer);
+            if (this.jmxmpObjectName != null) {
+                this.server.registerMBean(this.jmxmpConnectorServer, this.jmxmpObjectName);
+            }
+        }
+
         try {
             if (this.threaded) {
                 Thread connectorThread = new Thread(() -> {
                     try {
                         Thread.currentThread().setContextClassLoader(ConnectorServerFactory.class.getClassLoader());
                         connectorServer.start();
+                        if (jmxmpEnabled && jmxmpConnectorServer != null) {
+                            jmxmpConnectorServer.start();
+                        }
                     } catch (IOException ex) {
                         if (ex.getCause() instanceof BindException){
                             // we want just the port message
@@ -285,9 +333,17 @@
                 connectorThread.start();
             } else {
                 this.connectorServer.start();
+                if (jmxmpEnabled && jmxmpConnectorServer != null) {
+                    jmxmpConnectorServer.start();
+                }
             }
         } catch (Exception ex) {
-            doUnregister(this.objectName);
+            if (this.objectName != null) {
+                doUnregister(this.objectName);
+            }
+            if (jmxmpEnabled && this.jmxmpObjectName != null) {
+                doUnregister(this.jmxmpObjectName);
+            }
             throw ex;
         }
     }
@@ -297,8 +353,16 @@
             if (this.connectorServer != null) {
                 this.connectorServer.stop();
             }
+            if (this.jmxmpEnabled && this.jmxmpConnectorServer != null) {
+                this.jmxmpConnectorServer.stop();
+            }
         } finally {
-            doUnregister(this.objectName);
+            if (this.objectName != null) {
+                doUnregister(this.objectName);
+            }
+            if (this.jmxmpEnabled && this.jmxmpObjectName != null) {
+                doUnregister(this.jmxmpObjectName);
+            }
         }
     }
 
@@ -307,6 +371,9 @@
             if (this.objectName != null && this.server.isRegistered(objectName)) {
                 this.server.unregisterMBean(objectName);
             }
+            if (this.jmxmpObjectName != null && this.server.isRegistered(jmxmpObjectName)) {
+                this.server.unregisterMBean(jmxmpObjectName);
+            }
         } catch (JMException ex) {
             // Ignore
         }
diff --git a/management/server/src/main/java/org/apache/karaf/management/JaasAuthenticator.java b/management/server/src/main/java/org/apache/karaf/management/JaasAuthenticator.java
index 2f8aeb7..0ad401d 100644
--- a/management/server/src/main/java/org/apache/karaf/management/JaasAuthenticator.java
+++ b/management/server/src/main/java/org/apache/karaf/management/JaasAuthenticator.java
@@ -17,22 +17,27 @@
 package org.apache.karaf.management;
 
 import javax.security.auth.callback.Callback;
+
+import com.sun.jdmk.security.sasl.AuthenticateCallback;
 import org.apache.karaf.jaas.boot.principal.ClientPrincipal;
 import org.apache.karaf.jaas.boot.principal.RolePrincipal;
 
+import java.io.IOException;
 import java.rmi.server.RemoteServer;
 import java.security.Principal;
 
 import javax.management.remote.JMXAuthenticator;
 import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
 import javax.security.auth.callback.NameCallback;
 import javax.security.auth.callback.PasswordCallback;
 import javax.security.auth.callback.UnsupportedCallbackException;
 import javax.security.auth.login.FailedLoginException;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
+import javax.security.sasl.AuthorizeCallback;
 
-public class JaasAuthenticator implements JMXAuthenticator {
+public class JaasAuthenticator implements JMXAuthenticator, CallbackHandler {
 
     private String realm;
 
@@ -90,4 +95,52 @@
             throw new SecurityException("Authentication failed", e);
         }
     }
+
+    @Override
+    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+        for (int i = 0; i < callbacks.length; i++) {
+            if (callbacks[i] instanceof AuthenticateCallback) {
+                AuthenticateCallback acb = (AuthenticateCallback) callbacks[i];
+                Subject subject = new Subject();
+                try {
+                    subject.getPrincipals().add(new ClientPrincipal("jmx", RemoteServer.getClientHost()));
+                } catch (Throwable t) {
+                    // Ignore
+                }
+                try {
+                    LoginContext loginContext = new LoginContext(realm, subject, cb -> {
+                        for (Callback callback : cb) {
+                            if (callback instanceof NameCallback) {
+                                ((NameCallback) callback).setName(acb.getAuthenticationID());
+                            } else if (callback instanceof PasswordCallback) {
+                                ((PasswordCallback) callback).setPassword((acb.getPassword()));
+                            } else {
+                                throw new UnsupportedCallbackException(callback);
+                            }
+                        }
+                    });
+                    loginContext.login();
+                } catch (Exception e) {
+                    throw new SecurityException("Authentication failed", e);
+                }
+                int roleCount = 0;
+                for (Principal principal : subject.getPrincipals()) {
+                    if (principal instanceof RolePrincipal) {
+                        roleCount++;
+                    }
+                }
+
+                if (roleCount == 0) {
+                    throw new SecurityException("User doesn't have role defined");
+                }
+                acb.setAuthenticated(true);
+            } else if (callbacks[i] instanceof AuthorizeCallback) {
+                AuthorizeCallback acb = (AuthorizeCallback) callbacks[i];
+                acb.setAuthorized(true);
+            } else {
+                throw new UnsupportedCallbackException(callbacks[i]);
+            }
+        }
+    }
+
 }
diff --git a/management/server/src/main/java/org/apache/karaf/management/internal/Activator.java b/management/server/src/main/java/org/apache/karaf/management/internal/Activator.java
index d6ea93d..586c392 100644
--- a/management/server/src/main/java/org/apache/karaf/management/internal/Activator.java
+++ b/management/server/src/main/java/org/apache/karaf/management/internal/Activator.java
@@ -102,10 +102,14 @@
         String jmxRealm = getString("jmxRealm", "karaf");
         String serviceUrl = getString("serviceUrl",
                 "service:jmx:rmi://" + rmiServerHost + ":" + rmiServerPort + "/jndi/rmi://" + rmiRegistryHost + ":" + rmiRegistryPort + "/karaf-" + System.getProperty("karaf.name"));
-
+        boolean jmxmpEnabled = getBoolean("jmxmpEnabled", false);
+        String jmxmpHost = getString("jmxmpHost", "0.0.0.0");
+        int jmxmpPort = getInt("jmxmpPort", 9999);
+        String jmxmpServiceUrl = getString("jmxmpServiceUrl", "service:jmx:jmxmp://" + jmxmpHost + ":" + jmxmpPort);
         boolean daemon = getBoolean("daemon", true);
         boolean threaded = getBoolean("threaded", true);
         ObjectName objectName = new ObjectName(getString("objectName", "connector:name=rmi"));
+        ObjectName jmxmpObjectName = new ObjectName(getString("jmxmpObjectName", "connector:name=jmxmp"));
         long keyStoreAvailabilityTimeout = getLong("keyStoreAvailabilityTimeout", 5000);
         String authenticatorType = getString("authenticatorType", "password");
         final boolean secured = getBoolean("secured", false);
@@ -149,10 +153,17 @@
         connectorServerFactory.setDaemon(daemon);
         connectorServerFactory.setThreaded(threaded);
         connectorServerFactory.setObjectName(objectName);
+        connectorServerFactory.setJmxmpEnabled(jmxmpEnabled);
+        connectorServerFactory.setJmxmpServiceUrl(jmxmpServiceUrl);
+        connectorServerFactory.setJmxmpObjectName(jmxmpObjectName);
+        Map<String, Object> jmxmpEnvironment = new HashMap<>();
+        jmxmpEnvironment.put("jmx.remote.profiles", "SASL/PLAIN");
+        jmxmpEnvironment.put("jmx.remote.sasl.callback.handler", jaasAuthenticator);
         Map<String, Object> environment = new HashMap<>();
         environment.put("jmx.remote.authenticator", jaasAuthenticator);
         try {
             connectorServerFactory.setEnvironment(environment);
+            connectorServerFactory.setJmxmpEnvironment(jmxmpEnvironment);
             connectorServerFactory.setKeyStoreAvailabilityTimeout(keyStoreAvailabilityTimeout);
             connectorServerFactory.setAuthenticatorType(authenticatorType);
             connectorServerFactory.setSecured(secured);
diff --git a/management/server/src/main/java/org/apache/karaf/management/internal/JMXSecurityMBeanImpl.java b/management/server/src/main/java/org/apache/karaf/management/internal/JMXSecurityMBeanImpl.java
index 3c66b9f..59d1b59 100644
--- a/management/server/src/main/java/org/apache/karaf/management/internal/JMXSecurityMBeanImpl.java
+++ b/management/server/src/main/java/org/apache/karaf/management/internal/JMXSecurityMBeanImpl.java
@@ -109,7 +109,7 @@
                         table.put(data);
                     } catch (KeyAlreadyExistsException e) {
                         // KeyAlreadyExistsException can happen only when methods are not empty
-                        LOG.warn("{} (objectName = \"{}\", method = \"{}\")", e, objectName, method);
+                        LOG.warn("{} (objectName = \"{}\", method = \"{}\")", e, new Object[] { objectName, method });
                     }
                 }
             }
diff --git a/manual/src/main/asciidoc/user-guide/monitoring.adoc b/manual/src/main/asciidoc/user-guide/monitoring.adoc
index fc65480..a608f9a 100644
--- a/manual/src/main/asciidoc/user-guide/monitoring.adoc
+++ b/manual/src/main/asciidoc/user-guide/monitoring.adoc
@@ -94,6 +94,26 @@
 serviceUrl = service:jmx:rmi://0.0.0.0:${rmiServerPort}/jndi/rmi://0.0.0.0:${rmiRegistryPort}/karaf-${karaf.name}
 
 #
+# JMXMP connector enabled
+#
+jmxmpEnabled = false
+
+#
+# JMXMP connector host name
+#
+jmxmpHost = 127.0.0.1
+
+#
+# JMXMP connector port number
+#
+jmxmpPort = 9999
+
+#
+# JMXMP connector service URL
+#
+jmxmpServiceUrl = service:jmx:jmxmp://${jmxmpHost}:${jmxmpPort}
+
+#
 # Whether any threads started for the JMXConnectorServer should be started as daemon threads
 #
 daemon = true
@@ -109,6 +129,11 @@
 objectName = connector:name=rmi
 
 #
+# The ObjectName used to register the JMXMP connector
+#
+jmxmpObjectName = connector:name=jmxmp
+
+#
 # Role name used for JMX access authorization
 #
 # jmxRole=admin
@@ -118,6 +143,8 @@
 * `rmiServerPort` property contains the port number of the JMX RMI server. Default is `44444`.
 * `jmxRealm` is the security realm to use as authentication backend. By default it uses the `karaf` realm.
 
+By default, Karaf exposes JMX using RMI. You can also enable JMXMP connector by settings `jmxmpEnabled=true`. You can then configure the JMXMP connector using the corresponding `jmxmp*` properties.
+
 ==== MBeans
 
 Apache Karaf provides a bunch of MBeans.
diff --git a/pom.xml b/pom.xml
index 8945686..b90fe3f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -251,7 +251,7 @@
         <openjpa.version>3.0.0</openjpa.version>
         <geronimo.transaction.manager.version>3.1.3</geronimo.transaction.manager.version>
         <guava.version>20.0</guava.version>
-        <narayana.version>5.10.0.Final</narayana.version>
+        <narayana.version>5.10.1.Final</narayana.version>
         <hibernate.annotations.common.version>3.3.0.ga</hibernate.annotations.common.version>
         <hibernate.annotations.version>3.4.0.GA</hibernate.annotations.version>
         <hibernate.ejb.version>3.4.0.GA</hibernate.ejb.version>
@@ -260,7 +260,7 @@
         <hibernate43.version>4.3.6.Final</hibernate43.version>
         <hibernate52.version>5.2.18.Final</hibernate52.version>
         <hibernate.version>5.4.8.Final</hibernate.version>
-        <hibernate.validator.version>6.0.18.Final</hibernate.validator.version>
+        <hibernate.validator.version>6.1.0.Final</hibernate.validator.version>
         <httpclient.version>4.5.6</httpclient.version>
         <jansi.version>1.18</jansi.version>
         <javassist.version>3.9.0.GA</javassist.version>
@@ -269,7 +269,7 @@
         <junit.version>4.12</junit.version>
         <jsw.version>3.2.3</jsw.version>
         <log4j.version>1.2.17</log4j.version>
-        <maven.version>3.6.2</maven.version>
+        <maven.version>3.6.3</maven.version>
         <maven.wagon.version>3.2.0</maven.wagon.version>
         <maven-plugin-annotations.version>3.6.0</maven-plugin-annotations.version>
         <maven.resolver.version>1.3.1</maven.resolver.version>
@@ -281,7 +281,7 @@
         <pax.cdi.version>1.1.2</pax.cdi.version>
         <pax.exam.version>4.13.1</pax.exam.version>
         <pax.logging.version>1.11.3</pax.logging.version>
-        <pax.base.version>1.5.0</pax.base.version>
+        <pax.base.version>1.5.1</pax.base.version>
         <pax.swissbox.version>1.8.3</pax.swissbox.version>
         <pax.url.version>2.6.2</pax.url.version>
         <pax.web.version>7.2.12</pax.web.version>
@@ -1824,7 +1824,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-compiler-plugin</artifactId>
-                    <version>3.8.0</version>
+                    <version>3.8.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1844,17 +1844,17 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-javadoc-plugin</artifactId>
-                    <version>3.0.1</version>
+                    <version>3.1.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-jar-plugin</artifactId>
-                    <version>3.1.0</version>
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-remote-resources-plugin</artifactId>
-                    <version>1.5</version>
+                    <version>1.6.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1895,7 +1895,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-surefire-plugin</artifactId>
-                    <version>2.22.0</version>
+                    <version>2.22.2</version>
                     <configuration>
                             <forkMode>once</forkMode>
                             <argLine>${surefire.argLine}</argLine>
@@ -1914,12 +1914,12 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-source-plugin</artifactId>
-                    <version>3.0.1</version>
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-assembly-plugin</artifactId>
-                    <version>3.1.0</version>
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1977,7 +1977,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-war-plugin</artifactId>
-                    <version>3.2.2</version>
+                    <version>3.2.3</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.karaf.tooling</groupId>
@@ -2157,12 +2157,12 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-javadoc-plugin</artifactId>
-                <version>3.0.1</version>
+                <version>3.1.1</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-jxr-plugin</artifactId>
-                <version>2.5</version>
+                <version>3.0.0</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
@@ -2172,7 +2172,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-report-plugin</artifactId>
-                <version>2.22.0</version>
+                <version>2.22.2</version>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
@@ -2244,7 +2244,7 @@
                                 <!-- test json files -->
                                 <exclude>**/*.json</exclude>
                                 <!-- test wiring resource files -->
-                                <exclude>**/src/test/resources/wirings/1</exclude>
+                                <exclude>**/src/test/resources/wirings/*</exclude>
                                 <!-- SSH keys -->
 				                <exclude>**/*.key</exclude>
                                 <exclude>**/*.id_rsa</exclude>
diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml
index 4fa6718..893159d 100644
--- a/tooling/karaf-maven-plugin/pom.xml
+++ b/tooling/karaf-maven-plugin/pom.xml
@@ -253,7 +253,7 @@
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-invoker-plugin</artifactId>
-                <version>3.1.0</version>
+                <version>3.2.1</version>
                 <configuration>
                     <debug>true</debug>
                     <projectsDirectory>src/it</projectsDirectory>
@@ -318,7 +318,7 @@
             </plugin>
             <plugin>
                 <artifactId>maven-plugin-plugin</artifactId>
-                <version>3.5.2</version>
+                <version>3.6.0</version>
                 <executions>
                     <execution>
                         <id>default-descriptor</id>