Merge pull request #1128 from skitt/remove-spring-4.0

Remove Spring 4.0
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..9420e0a
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+notifications:
+  pullrequests: commits@karaf.apache.org
diff --git a/assemblies/docker/Dockerfile b/assemblies/docker/Dockerfile
index 6a9b8fe..09fa2ea 100644
--- a/assemblies/docker/Dockerfile
+++ b/assemblies/docker/Dockerfile
@@ -16,11 +16,12 @@
 # limitations under the License.
 ################################################################################
 
-FROM openjdk:8-jre
+FROM adoptopenjdk:11-jre-hotspot
 
 # Karaf environment variables
 ENV KARAF_INSTALL_PATH /opt
 ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf
+ENV KARAF_EXEC exec
 ENV PATH $PATH:$KARAF_HOME/bin
 #WORKDIR $KARAF_HOME
 
diff --git a/assemblies/docker/README.md b/assemblies/docker/README.md
index 0ddf667..fbb2bf6 100644
--- a/assemblies/docker/README.md
+++ b/assemblies/docker/README.md
@@ -26,9 +26,13 @@
 Install the most recent stable version of docker-compose
 https://docs.docker.com/compose/install/
 
+If you want to build multi-platform (OS/Arch) Docker images, then you must install
+[`buildx`](https://docs.docker.com/buildx/working-with-buildx/).
+On macOS, an easy way to install `buildx` is to install [Docker Desktop Edge](https://docs.docker.com/docker-for-mac/edge-release-notes/).
+
 ## Build
 
-Images are based on the official Java Alpine (OpenJDK 8) image. If you want to
+Images are based on the Docker official [AdoptOpenJDK 11 JRE Hotspot](https://hub.docker.com/_/adoptopenjdk?tab=tags&page=1&name=11-jre-hotspot) image. If you want to
 build the Karaf image run:
 
 ```
@@ -48,6 +52,55 @@
 docker build --build-arg KARAF_VERSION=4.2.0 -t "karaf:4.2.0" karaf
 ```
 
+If you want to build the container for a specific version of Karaf and
+specific version of the platform, and push the image to the Docker Hub repository,
+you can use this command (replacing the version, image name, and targets as appropriate):
+
+```
+./build.sh --from-release --karaf-version 4.2.9 --image-name amusarra/karaf:4.2.9 \
+ --build-multi-platform linux/arm64,linux/arm/v7,linux/amd64
+```
+
+Below is the output you should get from running the previous command.
+
+```
+Downloading apache-karaf-4.2.9.tar.gz from https://downloads.apache.org/karaf/4.2.9/
+Checking if buildx installed...
+Found buildx {github.com/docker/buildx v0.3.1-tp-docker 6db68d029599c6710a32aa7adcba8e5a344795a7} on your docker system
+Starting build of the docker image for the platform linux/arm64,linux/arm/v7,linux/amd64
+[+] Building 15.8s (16/16) FINISHED
+ => [internal] load build definition from Dockerfile                                                                                                                         0.0s
+ => => transferring dockerfile: 32B                                                                                                                                          0.0s
+ => [internal] load .dockerignore                                                                                                                                            0.1s
+ => => transferring context: 2B                                                                                                                                              0.0s
+ => [linux/arm64 internal] load metadata for docker.io/library/openjdk:8u212-jre-alpine                                                                                      2.5s
+ => [linux/arm/v7 internal] load metadata for docker.io/library/openjdk:8u212-jre-alpine                                                                                     2.6s
+ => [linux/amd64 internal] load metadata for docker.io/library/openjdk:8u212-jre-alpine                                                                                      2.5s
+ => [linux/amd64 1/3] FROM docker.io/library/openjdk:8u212-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193                                0.0s
+ => => resolve docker.io/library/openjdk:8u212-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193                                            0.0s
+ => [internal] load build context                                                                                                                                            1.7s
+ => => transferring context: 22.69MB                                                                                                                                         1.7s
+ => [linux/arm64 1/3] FROM docker.io/library/openjdk:8u212-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193                                0.0s
+ => [linux/arm/v7 1/3] FROM docker.io/library/openjdk:8u212-jre-alpine@sha256:f362b165b870ef129cbe730f29065ff37399c0aa8bcab3e44b51c302938c9193                               0.0s
+ => CACHED [linux/arm64 2/3] ADD _TMP_/apache-karaf-4.2.9.tar.gz /opt                                                                                                        0.0s
+ => CACHED [linux/arm64 3/3] RUN set -x &&   ln -s /opt/apache-karaf* /opt/apache-karaf                                                                                      0.0s
+ => CACHED [linux/amd64 2/3] ADD _TMP_/apache-karaf-4.2.9.tar.gz /opt                                                                                                        0.0s
+ => CACHED [linux/amd64 3/3] RUN set -x &&   ln -s /opt/apache-karaf* /opt/apache-karaf                                                                                      0.0s
+ => CACHED [linux/arm/v7 2/3] ADD _TMP_/apache-karaf-4.2.9.tar.gz /opt                                                                                                       0.0s
+ => CACHED [linux/arm/v7 3/3] RUN set -x &&   ln -s /opt/apache-karaf* /opt/apache-karaf                                                                                     0.0s
+ => exporting to image                                                                                                                                                      11.4s
+ => => exporting layers                                                                                                                                                      0.0s
+ => => exporting manifest sha256:205fe4347d83e0183ff479360733d6294a8d060127d5a87ae0e06e1f9b18f08e                                                                            0.0s
+ => => exporting config sha256:94a9fb41574916225852575d3f1eda6f267d3e83dfc81e262e8a766b5b36e92f                                                                              0.0s
+ => => exporting manifest sha256:acb94fccec1b975a62b7385cf227e01afb5875d74c24a5bef4546381fd2a483e                                                                            0.0s
+ => => exporting config sha256:d2b0989d52cd13f19a02f7e88544f1e184bc592178608fd79c88d635f751707a                                                                              0.0s
+ => => exporting manifest sha256:47c72f3cb18db75f63c21da1e475958580f3d9c935930e1e8b04ccc7ad0e8a37                                                                            0.0s
+ => => exporting config sha256:13d3f0dc19bec1fec60767a4f5d19750f02401efa0aabc7f83fc318a96eaf660                                                                              0.0s
+ => => exporting manifest list sha256:341588c548bfe56818c6435d3301fee6e2e6b4b06e5bb94b15102c0ca86a90e9                                                                       0.0s
+ => => pushing layers                                                                                                                                                        3.7s
+ => => pushing manifest for docker.io/amusarra/karaf:4.2.9
+```
+
 ## Run
 
 * Run Karaf with interactive mode
diff --git a/assemblies/docker/build.sh b/assemblies/docker/build.sh
index 3e79174..0277448 100755
--- a/assemblies/docker/build.sh
+++ b/assemblies/docker/build.sh
@@ -21,11 +21,14 @@
 usage() {
   cat <<HERE
 Usage:
-  build.sh --from-local-dist [--archive <archive>] [--image-name <image>]
-  build.sh --from-release --karaf-version <x.x.x> [--image-name <image>]
+  build.sh --from-local-dist [--archive <archive>] [--image-name <image>] [--build-multi-platform <comma-separated platforms>]
+  build.sh --from-release --karaf-version <x.x.x> [--image-name <image>] [--build-multi-platform <comma-separated platforms>]
   build.sh --help
 
   If the --image-name flag is not used the built image name will be 'karaf'.
+  Check the supported build platforms; you can verify with this command: `docker buildx ls`
+  The supported platforms (OS/Arch) depend on the build's base image, in this case [`openjdk:8u212-jre-alpine`](https://hub.docker.com/_/openjdk?tab=tags&page=1&name=8u212-jre-alpine).
+  
 HERE
   exit 1
 }
@@ -52,6 +55,10 @@
     KARAF_VERSION="$2"
     shift
     ;;
+    --build-multi-platform)
+    BUILD_MULTI_PLATFORM="$2"
+    shift
+    ;;
     --help)
     usage
     ;;
@@ -105,4 +112,22 @@
 
 fi
 
-docker build --build-arg karaf_dist="${KARAF_DIST}" -t "${IMAGE_NAME}" .
+if [ -n "${BUILD_MULTI_PLATFORM}" ]; then
+  echo "Checking if buildx installed..."
+  VERSION_BUILD_X=`docker buildx version` > /dev/null 2>&1
+
+  if [ $? -eq 0 ]; then
+    echo "Found buildx {${VERSION_BUILD_X}} on your docker system"
+    echo "Starting build of the docker image for the platform ${BUILD_MULTI_PLATFORM}"
+    
+    BUILD_X="buildx"
+    BUILD_X_FLAG="--push"
+    BUILD_X_PLATFORM="--platform ${BUILD_MULTI_PLATFORM}"
+  else
+    echo "Error: buildx not installed with your docker system"
+    exit -1
+  fi
+
+fi
+
+docker ${BUILD_X} build ${BUILD_X_PLATFORM} --build-arg karaf_dist="${KARAF_DIST}" ${BUILD_X_FLAG} -t "${IMAGE_NAME}" .
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/client b/assemblies/features/base/src/main/filtered-resources/resources/bin/client
index f258a4c..14a0935 100755
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/client
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/client
@@ -79,7 +79,9 @@
 setupClassPath() {
     # Add the jars in the lib dir
     CLASSPATH="${KARAF_HOME}/system/org/apache/karaf/org.apache.karaf.client/@@project.version@@/org.apache.karaf.client-@@project.version@@.jar"
-    CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/apache/sshd/sshd-core/@@sshd.version@@/sshd-core-@@sshd.version@@.jar"
+    CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/apache/sshd/sshd-osgi/@@sshd.version@@/sshd-osgi-@@sshd.version@@.jar"
+    CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/apache/sshd/sshd-scp/@@sshd.version@@/sshd-scp-@@sshd.version@@.jar"
+    CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/apache/sshd/sshd-sftp/@@sshd.version@@/sshd-sftp-@@sshd.version@@.jar"
     CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/fusesource/jansi/jansi/@@jansi.version@@/jansi-@@jansi.version@@.jar"
     CLASSPATH="${CLASSPATH}:${KARAF_HOME}/system/org/jline/jline/@@jline.version@@/jline-@@jline.version@@.jar"
 }
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/client.bat b/assemblies/features/base/src/main/filtered-resources/resources/bin/client.bat
index 412eb58..cbbddb0 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/client.bat
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/client.bat
@@ -215,7 +215,9 @@
 :KARAF_EXTRA_JAVA_OPTS_END

 

 set CLASSPATH=%KARAF_HOME%\system\org\apache\karaf\org.apache.karaf.client\@@project.version@@\org.apache.karaf.client-@@project.version@@.jar

-set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\apache\sshd\sshd-core\@@sshd.version@@\sshd-core-@@sshd.version@@.jar

+set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\apache\sshd\sshd-osgi\@@sshd.version@@\sshd-osgi-@@sshd.version@@.jar

+set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\apache\sshd\sshd-scp\@@sshd.version@@\sshd-scp-@@sshd.version@@.jar

+set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\apache\sshd\sshd-sftp\@@sshd.version@@\sshd-sftp-@@sshd.version@@.jar

 set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\jline\jline\@@jline.version@@\jline-@@jline.version@@.jar

 set CLASSPATH=%CLASSPATH%;%KARAF_HOME%\system\org\fusesource\jansi\jansi\@@jansi.version@@\jansi-@@jansi.version@@.jar

 

diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/inc b/assemblies/features/base/src/main/filtered-resources/resources/bin/inc
index fea17e2..c8f33ec 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/inc
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/inc
@@ -307,11 +307,11 @@
     if [ "x${JAVA_OPTS}" = "x" ]; then
         JAVA_OPTS="${DEFAULT_JAVA_OPTS}"
     fi
-    export JAVA_OPTS
 
     if [ "x${EXTRA_JAVA_OPTS}" != "x" ]; then
         JAVA_OPTS="${JAVA_OPTS} ${EXTRA_JAVA_OPTS}"
     fi
+    export JAVA_OPTS
 
 
     ##
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
index e5fca26..9b3b791 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf
@@ -314,9 +314,12 @@
                     --add-opens java.base/java.util=ALL-UNNAMED \
                     --add-opens java.naming/javax.naming.spi=ALL-UNNAMED \
                     --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED \
+                    --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED \
+                    --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED \
                     --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED \
                     --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED \
                     --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED \
+                    --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED \
                     --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED \
                     --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED \
                     -Dkaraf.instances="${KARAF_HOME}/instances" \
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
index a717221..77b51fd 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
+++ b/assemblies/features/base/src/main/filtered-resources/resources/bin/karaf.bat
@@ -440,9 +440,12 @@
                 --add-opens java.base/java.util=ALL-UNNAMED ^

                 --add-opens java.naming/javax.naming.spi=ALL-UNNAMED ^

                 --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED ^

+                --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED ^

+                --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED ^

                 --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED ^

                 --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED ^

                 --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED ^

+                --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED ^

                 --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED ^

                 --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED ^

                 -classpath "%CLASSPATH%" ^

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 859d074..217fabb 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
@@ -125,6 +125,24 @@
  osgi.service;objectClass:List<String>=org.eclipse.osgi.service.security.TrustEngine;osgi.signedcontent.trust.engine=org.eclipse.osgi, \
  osgi.service;objectClass:List<String>=org.eclipse.osgi.service.urlconversion.URLConverter;protocol:List<String>="bundleentry,bundleresource"
 
+eecap-15 = osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0"
+eecap-14 = osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0"
+eecap-13 = osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0"
 eecap-11= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
  osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0", \
  osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
diff --git a/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties b/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties
index 82345dc..0f408ab 100644
--- a/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties
+++ b/assemblies/features/base/src/main/filtered-resources/resources/etc/jre.properties
@@ -266,7 +266,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.2.1", \
  javax.xml.bind.annotation;version="2.2.1", \
@@ -419,7 +419,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.2.8", \
  javax.xml.bind.annotation;version="2.2.8", \
@@ -612,7 +612,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.3.0", \
  javax.xml.bind.annotation;version="2.3.0", \
@@ -693,3 +693,6 @@
 
 jre-10 = ${jre-9}
 jre-11 = ${jre-10}
+jre-13 = ${jre-11}
+jre-14 = ${jre-13}
+jre-15 = ${jre-14}
diff --git a/assemblies/features/base/src/main/resources/resources/bin/contrib/karaf-service.sh b/assemblies/features/base/src/main/resources/resources/bin/contrib/karaf-service.sh
index a095aea..0e3696d 100755
--- a/assemblies/features/base/src/main/resources/resources/bin/contrib/karaf-service.sh
+++ b/assemblies/features/base/src/main/resources/resources/bin/contrib/karaf-service.sh
@@ -139,7 +139,7 @@
 if [[ ! $KARAF_SERVICE_TEMPLATE ]]; then
     case $(uname | tr [:upper:] [:lower:]) in
         sunos)
-            # add KARAF_ENV vars to envirioment
+            # add KARAF_ENV vars to environment
             for var in "${KARAF_ENV[@]}"; do
                 export $var
             done
diff --git a/assemblies/features/base/src/main/resources/resources/etc/shell.init.script b/assemblies/features/base/src/main/resources/resources/etc/shell.init.script
index 2dff99c..d4e542a 100644
--- a/assemblies/features/base/src/main/resources/resources/etc/shell.init.script
+++ b/assemblies/features/base/src/main/resources/resources/etc/shell.init.script
@@ -30,6 +30,7 @@
 man = { help $args } ;
 log:list = { log:get ALL } ;
 service:get = { $.context getService ($.context getServiceReference $args) };
+feature:upgrade = { feature:install -u $args } ;
 
 env = { shell:set $args }
 edit = { shell:nano $args }
diff --git a/assemblies/features/enterprise/src/main/feature/feature.xml b/assemblies/features/enterprise/src/main/feature/feature.xml
index 63ff686..105a0ab 100644
--- a/assemblies/features/enterprise/src/main/feature/feature.xml
+++ b/assemblies/features/enterprise/src/main/feature/feature.xml
@@ -178,15 +178,15 @@
         <bundle>wrap:mvn:antlr/antlr/2.7.7</bundle>
         <bundle>mvn:javax.persistence/javax.persistence-api/2.2</bundle>
         <bundle>mvn:org.javassist/javassist/3.24.0-GA</bundle>
-        <bundle>mvn:net.bytebuddy/byte-buddy/1.9.10</bundle>
+        <bundle>mvn:net.bytebuddy/byte-buddy/1.10.10</bundle>
         <bundle>mvn:org.jboss.spec.javax.transaction/jboss-transaction-api_1.2_spec/1.1.1.Final</bundle>
-        <bundle>mvn:org.jboss/jandex/2.0.5.Final</bundle>
-        <bundle>mvn:com.fasterxml/classmate/1.3.4</bundle>
+        <bundle>mvn:org.jboss/jandex/2.1.3.Final</bundle>
+        <bundle>mvn:com.fasterxml/classmate/1.5.1</bundle>
         <bundle>wrap:mvn:org.dom4j/dom4j/2.1.1</bundle>
         <bundle>mvn:org.hibernate.common/hibernate-commons-annotations/5.1.0.Final</bundle>
-        <bundle>mvn:org.hibernate/hibernate-core/5.4.2.Final</bundle>
+        <bundle>mvn:org.hibernate/hibernate-core/${hibernate.version}</bundle>
         <bundle>mvn:javax.interceptor/javax.interceptor-api/1.2</bundle>
-        <bundle>mvn:org.hibernate/hibernate-osgi/5.4.2.Final</bundle>
+        <bundle>mvn:org.hibernate/hibernate-osgi/${hibernate.version}</bundle>
         <capability>
             osgi.service;objectClass=javax.persistence.spi.PersistenceProvider;effective:=active;javax.persistence.provider=org.hibernate.jpa.HibernatePersistenceProvider
         </capability>
@@ -194,6 +194,7 @@
 
     <feature name="hibernate-envers" version="${hibernate.version}" description="Feature for easily adding Envers support to hibernate">
         <feature version="${hibernate.version}">hibernate</feature>
+        <bundle>mvn:org.jboss/jandex/2.1.3.Final</bundle>
         <bundle>mvn:org.hibernate/hibernate-envers/${hibernate.version}</bundle>
     </feature>
 
diff --git a/assemblies/features/spring/src/main/feature/feature.xml b/assemblies/features/spring/src/main/feature/feature.xml
index 49dce1d..28501df 100644
--- a/assemblies/features/spring/src/main/feature/feature.xml
+++ b/assemblies/features/spring/src/main/feature/feature.xml
@@ -99,6 +99,22 @@
 
     <!-- Spring Security -->
 
+    <feature name="spring-security" description="Spring Security 5.3.x support" version="${spring.security53.version}">
+        <feature>war</feature>
+        <feature version="[5.1,6)">spring-jdbc</feature>
+        <feature version="[5.1,6)">spring-tx</feature>
+        <feature version="[5.1,6)">spring-web</feature>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-core/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-annotations/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:com.fasterxml.jackson.core/jackson-databind/${jackson.version}</bundle>
+        <bundle dependency="true">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.aspectj/${aspectj.bundle.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-security-core/${spring.security53.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-security-config/${spring.security53.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-security-web/${spring.security53.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-security-acl/${spring.security53.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.servicemix.bundles/org.apache.servicemix.bundles.spring-security-taglibs/${spring.security53.version}</bundle>
+    </feature>
+
     <feature name="spring-security" description="Spring Security 5.1.x support" version="${spring.security51.version}">
         <feature>war</feature>
         <feature version="[5.1,6)">spring-jdbc</feature>
diff --git a/assemblies/features/standard/pom.xml b/assemblies/features/standard/pom.xml
index 82f4e6b..a9dc5a1 100644
--- a/assemblies/features/standard/pom.xml
+++ b/assemblies/features/standard/pom.xml
@@ -311,7 +311,17 @@
         <!-- ssh deps -->
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-scp</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-sftp</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml
index 2a59eca..ccc27bb 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -1027,7 +1027,9 @@
     <feature name="ssh" description="Provide a SSHd server on Karaf" version="${project.version}">
         <feature>shell</feature>
         <feature>jaas</feature>
-        <bundle start-level="30">mvn:org.apache.sshd/sshd-core/${sshd.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.sshd/sshd-osgi/${sshd.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.sshd/sshd-scp/${sshd.version}</bundle>
+        <bundle start-level="30">mvn:org.apache.sshd/sshd-sftp/${sshd.version}</bundle>
         <bundle start-level="30">mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle.version}</bundle>
         <bundle start-level="30">mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle.version}</bundle>
         <bundle start-level="30">mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/${project.version}</bundle>
diff --git a/assemblies/features/static/src/main/feature/feature.xml b/assemblies/features/static/src/main/feature/feature.xml
index 9ff34e7..0e82d87 100644
--- a/assemblies/features/static/src/main/feature/feature.xml
+++ b/assemblies/features/static/src/main/feature/feature.xml
@@ -25,6 +25,7 @@
         <bundle start="true" start-level="8">mvn:org.fusesource.jansi/jansi/${jansi.version}</bundle>
         <!-- static config admin -->
         <bundle start="true" start-level="10">mvn:org.apache.karaf.services/org.apache.karaf.services.staticcm/${project.version}</bundle>
+        <bundle dependency="true" start-level="30">mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.activation-api-1.1/${servicemix-spec.version}</bundle>
     </feature>
 
     <feature version="${project.version}" description="Karaf core feature" name="static-framework-logback">
@@ -33,6 +34,7 @@
         <bundle start="true" start-level="8">mvn:org.ops4j.pax.logging/pax-logging-logback/${pax.logging.version}</bundle>
         <!-- static config admin -->
         <bundle start="true" start-level="10">mvn:org.apache.karaf.services/org.apache.karaf.services.staticcm/${project.version}</bundle>
+        <bundle dependency="true" start-level="30">mvn:org.apache.servicemix.specs/org.apache.servicemix.specs.activation-api-1.1/${servicemix-spec.version}</bundle>
     </feature>
 
 </features>
diff --git a/client/pom.xml b/client/pom.xml
index 9eea90f..79f6e36 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -44,7 +44,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
         </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
diff --git a/client/src/main/java/org/apache/karaf/client/ClientConfig.java b/client/src/main/java/org/apache/karaf/client/ClientConfig.java
index 8f57481..6fefd90 100644
--- a/client/src/main/java/org/apache/karaf/client/ClientConfig.java
+++ b/client/src/main/java/org/apache/karaf/client/ClientConfig.java
@@ -213,7 +213,7 @@
         System.out.println("  -b            batch mode, specify multiple commands via standard input");

         System.out.println("  -f [file]     read commands from the specified file");

         System.out.println("  -k [keyFile]  specify the private keyFile location when using key login, need have BouncyCastle registered as security provider using this flag");

-        System.out.println("  -t [timeout]  define the client idle timeout");

+        System.out.println("  -t [timeout]  define the client idle timeout (in milliseconds)");

         System.out.println("  [commands] [--]   commands to run");

         System.out.println("If no commands are specified, the client will be put in an interactive mode");

         System.exit(0);

diff --git a/client/src/main/java/org/apache/karaf/client/Main.java b/client/src/main/java/org/apache/karaf/client/Main.java
index 8f72b84..f7f50da 100644
--- a/client/src/main/java/org/apache/karaf/client/Main.java
+++ b/client/src/main/java/org/apache/karaf/client/Main.java
@@ -97,7 +97,7 @@
             FilePasswordProvider passwordProvider = null;
             final Console console = System.console();
             if (console != null) {
-                passwordProvider = resourceKey -> {
+                passwordProvider = (session, resourceKey, retryIndex) -> {
                     char[] pwd = console.readPassword("Enter password for " + resourceKey + ": ");
                     return new String(pwd);
                 };
@@ -165,7 +165,7 @@
             client.getProperties().put(ClientFactoryManager.NIO2_READ_TIMEOUT, String.valueOf(config.getIdleTimeout()));
 
             // TODO: remove the line below when SSHD-732 is fixed
-            client.setKeyPairProvider(new FileKeyPairProvider());
+            // client.setKeyPairProvider(new FileKeyPairProvider());
             client.start();
             ClientSession session = connectWithRetries(client, config);
             if (config.getPassword() != null) {
@@ -383,7 +383,7 @@
             if (keyFile != null) {
                 FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(Paths.get(keyFile));
                 fileKeyPairProvider.setPasswordFinder(passwordProvider);
-                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                for (KeyPair key : fileKeyPairProvider.loadKeys(null)) {
                     agent.addIdentity(key, user);
                 }
             }
diff --git a/examples/karaf-itest-example/pom.xml b/examples/karaf-itest-example/pom.xml
index 42ace33..9b2e9db 100644
--- a/examples/karaf-itest-example/pom.xml
+++ b/examples/karaf-itest-example/pom.xml
@@ -75,7 +75,7 @@
         <dependency>
             <groupId>org.apache.geronimo.specs</groupId>
             <artifactId>geronimo-atinject_1.0_spec</artifactId>
-            <version>1.1</version>
+            <version>1.2</version>
             <scope>test</scope>
         </dependency>
         <dependency>
diff --git a/features/core/pom.xml b/features/core/pom.xml
index 9ac559d..67b9571 100644
--- a/features/core/pom.xml
+++ b/features/core/pom.xml
@@ -156,10 +156,9 @@
                 <configuration>
                     <instructions>
                         <Export-Package>
-                            org.apache.karaf.features;
-                            org.apache.karaf.features.management;
-                            org.apache.karaf.features.management.codec;
-                                -noimport:=true,
+                            org.apache.karaf.features,
+                            org.apache.karaf.features.management,
+                            org.apache.karaf.features.management.codec;-noimport:=true,
                             org.osgi.service.repository,
                             org.eclipse.equinox.region.*
                         </Export-Package>
@@ -206,7 +205,7 @@
 	        <id>java9-plus</id>
 	        <properties>
                 <plexus-utils.version>3.1.0</plexus-utils.version>
-                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
+                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
 	        </properties>
                 <dependencies>
                     <dependency>
diff --git a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
index cbb811b..f330e34 100644
--- a/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
+++ b/features/core/src/main/java/org/apache/karaf/features/internal/service/FeatureConfigInstaller.java
@@ -25,7 +25,12 @@
 import java.net.MalformedURLException;
 import java.net.URI;
 import java.net.URL;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 
 import org.apache.felix.utils.properties.InterpolationHelper;
 import org.apache.felix.utils.properties.TypedProperties;
@@ -62,36 +67,40 @@
         this.configCfgStore = configCfgStore;
     }
 
-    private ConfigId parsePid(String pid) {
-        int n = pid.indexOf('-');
-        ConfigId cid = new ConfigId();
-        cid.fullPid = pid;
+    private ConfigId parsePid(final String pid) {
+        final ConfigId cid = new ConfigId();
+        cid.pid = pid;
+        final int n = pid.contains("~") ? pid.indexOf('~') : pid.indexOf('-');
         if (n > 0) {
-            cid.factoryPid = pid.substring(n + 1);
-            cid.pid = pid.substring(0, n);
-        } else {
-            cid.pid = pid;
+            cid.isFactoryPid = true;
+            cid.factoryPid = pid.substring(0, n);
+            if (pid.contains("~")) {
+                cid.name = pid.substring(n + 1);
+            }
         }
         return cid;
     }
 
-    private Configuration createConfiguration(ConfigurationAdmin configurationAdmin, String pid,
-                                              String factoryPid)
+    private Configuration createConfiguration(ConfigurationAdmin configurationAdmin, ConfigId cid)
         throws IOException, InvalidSyntaxException {
-        if (factoryPid != null) {
-            return configurationAdmin.createFactoryConfiguration(pid, null);
+        if (cid.isFactoryPid) {
+            if (Objects.nonNull(cid.name)) {
+                return configurationAdmin.getFactoryConfiguration(cid.factoryPid, cid.name, null);
+            } else {
+                return configurationAdmin.createFactoryConfiguration(cid.factoryPid, null);
+            }
         } else {
-            return configurationAdmin.getConfiguration(pid, null);
+            return configurationAdmin.getConfiguration(cid.pid, null);
         }
     }
 
     private Configuration findExistingConfiguration(ConfigurationAdmin configurationAdmin, ConfigId cid)
         throws IOException, InvalidSyntaxException {
         String filter;
-        if (cid.factoryPid == null) {
+        if (!cid.isFactoryPid) {
             filter = "(" + Constants.SERVICE_PID + "=" + cid.pid + ")";
         } else {
-            filter = "(" + CONFIG_KEY + "=" + cid.fullPid + ")";
+            filter = "(" + CONFIG_KEY + "=" + cid.pid + ")";
         }
         Configuration[] configurations = configurationAdmin.listConfigurations(filter);
         return (configurations != null && configurations.length > 0) ? configurations[0] : null;
@@ -117,13 +126,13 @@
 
                 File cfgFile = null;
                 if (storage != null) {
-                    cfgFile = new File(storage, cid.fullPid + ".cfg");
+                    cfgFile = new File(storage, cid.pid + ".cfg");
                 }
                 if (!cfgFile.exists() || config.isOverride()) {
                     Dictionary<String, Object> cfgProps = convertToDict(props);
-                    cfg = createConfiguration(configAdmin, cid.pid, cid.factoryPid);
-                    cfgProps.put(CONFIG_KEY, cid.fullPid);
-                    props.put(CONFIG_KEY, cid.fullPid);
+                    cfg = createConfiguration(configAdmin, cid);
+                    cfgProps.put(CONFIG_KEY, cid.pid);
+                    props.put(CONFIG_KEY, cid.pid);
                     if (storage != null && configCfgStore) {
                         cfgProps.put(FILEINSTALL_FILE_NAME, cfgFile.getAbsoluteFile().toURI().toString());
                     }
@@ -172,7 +181,7 @@
                     }
                     File cfgFile = null;
                     if (storage != null) {
-                        cfgFile = new File(storage, configId.fullPid + ".cfg");
+                        cfgFile = new File(storage, configId.pid + ".cfg");
                     }
                     if (cfgFile.exists()) {
                         cfgFile.delete();
@@ -304,7 +313,7 @@
     private File getConfigFile(ConfigId cid) throws IOException, InvalidSyntaxException {
         Configuration cfg = findExistingConfiguration(configAdmin, cid);
         // update the cfg file depending of the configuration
-        File cfgFile = new File(storage, cid.fullPid + ".cfg");
+        File cfgFile = new File(storage, cid.pid + ".cfg");
         if (cfg != null && cfg.getProperties() != null) {
             Object val = cfg.getProperties().get(FILEINSTALL_FILE_NAME);
             try {
@@ -364,9 +373,11 @@
             || FILEINSTALL_FILE_NAME.equals(key);
     }
 
-    class ConfigId {
-        String fullPid;
+    private static final class ConfigId {
+        boolean isFactoryPid;
         String pid;
         String factoryPid;
+        String name;
     }
+
 }
diff --git a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
index 91f73b8..61b8786 100644
--- a/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
+++ b/instance/src/main/java/org/apache/karaf/instance/core/internal/InstanceServiceImpl.java
@@ -526,9 +526,12 @@
                       " --add-opens java.base/java.util=ALL-UNNAMED" +
                       " --add-opens java.naming/javax.naming.spi=ALL-UNNAMED" +
                       " --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED" +
+                      " --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED" +
+                      " --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED" +
                       " --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED" +
                       " --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED" +
                       " --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED" +
+                      " --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED" +
                       " --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED" +
                       " --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED";
         } else {
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 629111c..01cb120 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
@@ -129,6 +129,24 @@
  osgi.service;objectClass:List<String>=org.eclipse.osgi.service.security.TrustEngine;osgi.signedcontent.trust.engine=org.eclipse.osgi, \
  osgi.service;objectClass:List<String>=org.eclipse.osgi.service.urlconversion.URLConverter;protocol:List<String>="bundleentry,bundleresource"
 
+eecap-15= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,15.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,15.0"
+eecap-14= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0"
+eecap-13= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0"
 eecap-11= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
  osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0", \
  osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
diff --git a/itests/common/src/main/java/org/apache/karaf/itests/KarafTestSupport.java b/itests/common/src/main/java/org/apache/karaf/itests/KarafTestSupport.java
index 10f3b71..4a9c32a 100644
--- a/itests/common/src/main/java/org/apache/karaf/itests/KarafTestSupport.java
+++ b/itests/common/src/main/java/org/apache/karaf/itests/KarafTestSupport.java
@@ -230,9 +230,12 @@
                 new VMOption("java.naming/javax.naming.spi=ALL-UNNAMED"),
                 new VMOption("--add-opens"),
                 new VMOption("java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED"),
                 new VMOption("--add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED"),
                 new VMOption("-classpath"),
                 new VMOption("lib/jdk9plus/*" + File.pathSeparator + "lib/boot/*")
diff --git a/itests/test/pom.xml b/itests/test/pom.xml
index 8f75db5..ede2152 100644
--- a/itests/test/pom.xml
+++ b/itests/test/pom.xml
@@ -316,6 +316,7 @@
                         <spring.security31.version>${spring.security31.version}</spring.security31.version>
                         <spring.security42.version>${spring.security42.version}</spring.security42.version>
                         <spring.security51.version>${spring.security51.version}</spring.security51.version>
+                        <spring.security53.version>${spring.security53.version}</spring.security53.version>
                         <activemq.version>${activemq.version}</activemq.version>
                     </systemPropertyVariables>
                     <excludes>
@@ -378,6 +379,7 @@
                                 <spring.security31.version>${spring.security31.version}</spring.security31.version>
                                 <spring.security42.version>${spring.security42.version}</spring.security42.version>
                                 <spring.security51.version>${spring.security51.version}</spring.security51.version>
+                                <spring.security53.version>${spring.security53.version}</spring.security53.version>
                                 <activemq.version>${activemq.version}</activemq.version>
                     		</systemPropertyVariables>
                         </configuration>
diff --git a/itests/test/src/test/filtered-resources/etc/config.properties b/itests/test/src/test/filtered-resources/etc/config.properties
index 1ea7b7b..da920a9 100644
--- a/itests/test/src/test/filtered-resources/etc/config.properties
+++ b/itests/test/src/test/filtered-resources/etc/config.properties
@@ -99,6 +99,25 @@
  osgi.service;effective:=active;objectClass=org.osgi.service.resolver.Resolver, \
  osgi.service;effective:=active;objectClass=org.osgi.service.startlevel.StartLevel, \
  osgi.service;effective:=active;objectClass=org.osgi.service.url.URLHandlers
+
+eecap-15= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0"
+eecap-14= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0"
+eecap-13= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0"
 eecap-11= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
  osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0", \
  osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
diff --git a/itests/test/src/test/filtered-resources/etc/feature.xml b/itests/test/src/test/filtered-resources/etc/feature.xml
index 6b2cbe5..4fbf536 100644
--- a/itests/test/src/test/filtered-resources/etc/feature.xml
+++ b/itests/test/src/test/filtered-resources/etc/feature.xml
@@ -680,7 +680,9 @@
     <feature name="ssh" description="Provide a SSHd server on Karaf" version="${project.version}">
         <feature>shell</feature>
         <feature>jaas</feature>
-        <bundle start="true" start-level="30">mvn:org.apache.sshd/sshd-core/${sshd.version}</bundle>
+        <bundle start="true" start-level="30">mvn:org.apache.sshd/sshd-osgi/${sshd.version}</bundle>
+        <bundle start="true" start-level="30">mvn:org.apache.sshd/sshd-scp/${sshd.version}</bundle>
+        <bundle start="true" start-level="30">mvn:org.apache.sshd/sshd-sftp/${sshd.version}</bundle>
         <bundle start="true" start-level="30">mvn:org.bouncycastle/bcprov-jdk15on/${bouncycastle.version}</bundle>
         <bundle start="true" start-level="30">mvn:org.bouncycastle/bcpkix-jdk15on/${bouncycastle.version}</bundle>
         <bundle start="true" start-level="30">mvn:org.apache.karaf.shell/org.apache.karaf.shell.ssh/${project.version}</bundle>
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/BaseTest.java b/itests/test/src/test/java/org/apache/karaf/itests/BaseTest.java
index 74d91a9..e8949b2 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/BaseTest.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/BaseTest.java
@@ -36,6 +36,7 @@
                 KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "spring.security31.version", System.getProperty("spring.security31.version")),
                 KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "spring.security42.version", System.getProperty("spring.security42.version")),
                 KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "spring.security51.version", System.getProperty("spring.security51.version")),
+                KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "spring.security53.version", System.getProperty("spring.security53.version")),
                 KarafDistributionOption.editConfigurationFilePut("etc/system.properties", "activemq.version", System.getProperty("activemq.version"))
         };
         return Stream.of(super.config(), options).flatMap(Stream::of).toArray(Option[]::new);
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/examples/HttpResourceExampleTest.java b/itests/test/src/test/java/org/apache/karaf/itests/examples/HttpResourceExampleTest.java
index cfe76eb..3ed7a11 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/examples/HttpResourceExampleTest.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/examples/HttpResourceExampleTest.java
@@ -21,7 +21,7 @@
 import org.junit.runner.RunWith;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
-import org.ops4j.pax.exam.spi.reactors.PerMethod;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
 
 import java.io.BufferedReader;
 import java.io.InputStreamReader;
@@ -29,18 +29,22 @@
 import java.net.URL;
 
 @RunWith(PaxExam.class)
-@ExamReactorStrategy(PerMethod.class)
+@ExamReactorStrategy(PerClass.class)
 public class HttpResourceExampleTest extends BaseTest {
 
-    @Test
+    @Test(timeout = 60000L)
     public void test() throws Exception {
         addFeaturesRepository("mvn:org.apache.karaf.examples/karaf-http-resource-example-features/" + System.getProperty("karaf.version") + "/xml");
 
         installAndAssertFeature("karaf-http-resource-example-whiteboard");
 
         String command = executeCommand("http:list");
-        System.out.println(command);
+        while (!command.contains("Deployed")) {
+            Thread.sleep(200);
+            command = executeCommand("http:list");
+        }
         assertContains("ResourceServlet", command);
+        assertContains("Deployed", command);
 
         URL url = new URL("http://localhost:" + getHttpPort() + "/example/index.html");
         HttpURLConnection connection = (HttpURLConnection) url.openConnection();
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/mavenresolver/KarafMinimalMonitoredTestSupport.java b/itests/test/src/test/java/org/apache/karaf/itests/mavenresolver/KarafMinimalMonitoredTestSupport.java
index 2a5cd7b..d8bcedb 100644
--- a/itests/test/src/test/java/org/apache/karaf/itests/mavenresolver/KarafMinimalMonitoredTestSupport.java
+++ b/itests/test/src/test/java/org/apache/karaf/itests/mavenresolver/KarafMinimalMonitoredTestSupport.java
@@ -110,9 +110,12 @@
                 new VMOption("java.naming/javax.naming.spi=ALL-UNNAMED"),
                 new VMOption("--add-opens"),
                 new VMOption("java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED"),
                 new VMOption("--add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED"),
+                new VMOption("--add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED"),
                 new VMOption("--add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED"),
                 new VMOption("-classpath"),
                 new VMOption("lib/jdk9plus/*" + File.pathSeparator + "lib/boot/*")
diff --git a/jaas/command/src/main/java/org/apache/karaf/jaas/command/ManageRealmCommand.java b/jaas/command/src/main/java/org/apache/karaf/jaas/command/ManageRealmCommand.java
index f7855aa..05c4fc0 100644
--- a/jaas/command/src/main/java/org/apache/karaf/jaas/command/ManageRealmCommand.java
+++ b/jaas/command/src/main/java/org/apache/karaf/jaas/command/ManageRealmCommand.java
@@ -35,14 +35,14 @@
 @Service
 public class ManageRealmCommand extends JaasCommandSupport {
 
-    @Option(name = "--realm", description = "JAAS Realm", required = false, multiValued = false)
+    @Option(name = "--realm", description = "JAAS Realm. Must be combined with --module", required = false, multiValued = false)
     @Completion(RealmCompleter.class)
     String realmName;
 
     @Option(name = "--index", description = "Realm Index", required = false, multiValued = false)
     int index;
 
-    @Option(name = "--module", description = "JAAS Login Module Class Name", required = false, multiValued = false)
+    @Option(name = "--module", description = "JAAS Login Module Class Name. Must be combined with --realm", required = false, multiValued = false)
     @Completion(LoginModuleNameCompleter.class)
     String moduleName;
 
diff --git a/jaas/modules/pom.xml b/jaas/modules/pom.xml
index 7ed5184..821ab3d 100644
--- a/jaas/modules/pom.xml
+++ b/jaas/modules/pom.xml
@@ -117,7 +117,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
@@ -195,7 +195,7 @@
 	        <id>java9-plus</id>
 	        <properties>
                 <plexus-utils.version>3.1.0</plexus-utils.version>
-                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
+                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
 	        </properties>
                 <dependencies>
                     <dependency>
diff --git a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
index f85b271..97aa624 100644
--- a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
+++ b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/NamePubkeyCallbackHandler.java
@@ -18,6 +18,7 @@
 import java.nio.file.Path;
 import java.security.KeyPair;
 import java.security.PublicKey;
+import java.util.Iterator;
 import java.util.Objects;
 import javax.security.auth.callback.Callback;
 import javax.security.auth.callback.CallbackHandler;
@@ -43,11 +44,11 @@
         this.name = Objects.requireNonNull(name);
 
         FileKeyPairProvider provider = new FileKeyPairProvider(publicKeyFile);
-        Iterable<KeyPair> keys = provider.loadKeys();
-        if (!keys.iterator().hasNext()) {
+        Iterator<KeyPair> keys = provider.loadKeys(null).iterator();
+        if (!keys.hasNext()) {
             throw new IOException("no public keys loaded");
         }
-        this.publicKey = keys.iterator().next().getPublic();
+        this.publicKey = keys.next().getPublic();
     }
 
     @Override
diff --git a/main/src/test/resources/test-karaf-home/etc/config.properties b/main/src/test/resources/test-karaf-home/etc/config.properties
index 23f2171..79592b4 100755
--- a/main/src/test/resources/test-karaf-home/etc/config.properties
+++ b/main/src/test/resources/test-karaf-home/etc/config.properties
@@ -75,6 +75,25 @@
 
 org.osgi.framework.system.capabilities= \
  ${eecap-${java.specification.version}}
+
+eecap-15= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0,15.0"
+eecap-14= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0,14.0"
+eecap-13= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
+ osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
+ osgi.ee; osgi.ee="JavaSE/compact1"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact2"; version:List<Version>="1.8,9.0,10.0,11.0,13.0", \
+ osgi.ee; osgi.ee="JavaSE/compact3"; version:List<Version>="1.8,9.0,10.0,11.0,13.0"
 eecap-11= osgi.ee; osgi.ee="OSGi/Minimum"; version:List<Version>="1.0,1.1,1.2", \
  osgi.ee; osgi.ee="JavaSE"; version:List<Version>="1.0,1.1,1.2,1.3,1.4,1.5,1.6,1.7,1.8,9.0,10.0,11.0", \
  osgi.ee; osgi.ee="JRE"; version:List<Version>="1.0,1.1", \
diff --git a/main/src/test/resources/test-karaf-home/etc/jre.properties b/main/src/test/resources/test-karaf-home/etc/jre.properties
index c0ec626..a1b74a3 100644
--- a/main/src/test/resources/test-karaf-home/etc/jre.properties
+++ b/main/src/test/resources/test-karaf-home/etc/jre.properties
@@ -266,7 +266,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.2.1", \
  javax.xml.bind.annotation;version="2.2.1", \
@@ -428,7 +428,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.2.8", \
  javax.xml.bind.annotation;version="2.2.8", \
@@ -621,7 +621,7 @@
  javax.swing.tree, \
  javax.swing.undo, \
  javax.tools, \
- javax.transaction; javax.transaction.xa; partial=true; mandatory:=partial, \
+ javax.transaction; javax.transaction.xa; version="1.1"; partial=true; mandatory:=partial, \
  javax.xml, \
  javax.xml.bind;version="2.3.0", \
  javax.xml.bind.annotation;version="2.3.0", \
@@ -707,3 +707,6 @@
  org.xml.sax.helpers
 jre-10 = ${jre-9}
 jre-11 = ${jre-10}
+jre-13 = ${jre-11}
+jre-14 = ${jre-13}
+jre-15 = ${jre-14}
diff --git a/management/server/src/main/java/org/apache/karaf/management/KarafMBeanServerGuard.java b/management/server/src/main/java/org/apache/karaf/management/KarafMBeanServerGuard.java
index a255587..43c258e 100644
--- a/management/server/src/main/java/org/apache/karaf/management/KarafMBeanServerGuard.java
+++ b/management/server/src/main/java/org/apache/karaf/management/KarafMBeanServerGuard.java
@@ -106,7 +106,7 @@
         } else if ("setAttributes".equals(method.getName())) {
             handleSetAttributes(mbs, objectName, (AttributeList) args[1]);
         } else if ("invoke".equals(method.getName())) {
-            handleInvoke(objectName, (String) args[1], (Object[]) args[2], (String[]) args[3]);
+            handleInvoke(mbs, objectName, (String) args[1], (Object[]) args[2], (String[]) args[3]);
         }
 
         return null;
@@ -345,11 +345,20 @@
         return false;
     }
 
-    void handleInvoke(ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException {
-        handleInvoke(null, objectName, operationName, params, signature);
+    void handleInvoke(MBeanServer mbs, ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException, InstanceNotFoundException {
+        handleInvoke(mbs, null, objectName, operationName, params, signature);
     }
 
-    void handleInvoke(BulkRequestContext context, ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException {
+    void handleInvoke(MBeanServer mbs, BulkRequestContext context, ObjectName objectName, String operationName, Object[] params, String[] signature) throws IOException, InstanceNotFoundException {
+        if (mbs != null && mbs.isInstanceOf(objectName, "javax.management.loading.MLet")
+            && ("addUrl".equals(operationName) || "getMBeansFromURL".equals(operationName))) {
+            SecurityException se = new SecurityException(operationName + " is not allowed to be invoked");
+            if (logger != null) {
+                logger.log(INVOKE, INVOKE_SIG, null, se, objectName, operationName, signature, params);
+            }
+            throw se;
+        }
+
         if (context == null) {
             context = BulkRequestContext.newContext(configAdmin);
         }
diff --git a/manual/src/main/asciidoc/developer-guide/debugging.adoc b/manual/src/main/asciidoc/developer-guide/debugging.adoc
index 926e9d4..c5e4fba 100644
--- a/manual/src/main/asciidoc/developer-guide/debugging.adoc
+++ b/manual/src/main/asciidoc/developer-guide/debugging.adoc
@@ -85,20 +85,21 @@
 
 Last, inside your IDE, connect to the remote application (the default port to connect to is 5005).
 
-This option works fine when it is needed to debug a project deployed on top of Apache Karaf. Nervertheless, you will be blocked
-if you would like to debug the Karaf server itself. In this case, you can change the following parameter suspend=y in the
-karaf.bat script file. That will cause the JVM to pause just before running main() until you attach a debugger then it
+This option works fine when it is needed to debug a project deployed on top of Apache Karaf. Nevertheless, you will be blocked
+if you would like to debug the Karaf server itself.
+
+In this case, you can use `debugs` option (s as syspend). That will cause the JVM to pause just before running main() until you attach a debugger then it
 will resume the execution.  This way you can set your breakpoints anywhere in the code and you should hit them no matter
 how early in the startup they are.
 
 ----
-export DEFAULT_JAVA_DEBUG_OPTS='-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'
+bin/karaf debugs
 ----
 
 and on Windows,
 
 ----
-set DEFAULT_JAVA_DEBUG_OPTS='-Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005'
+bin\karaf.bat debugs
 ----
 
 ===== Debugging Environment Variables
diff --git a/manual/src/main/asciidoc/user-guide/interceptor.adoc b/manual/src/main/asciidoc/user-guide/interceptor.adoc
new file mode 100644
index 0000000..8291de7
--- /dev/null
+++ b/manual/src/main/asciidoc/user-guide/interceptor.adoc
@@ -0,0 +1,112 @@
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+= Interceptor
+
+Interceptor module is inspired from JavaEE/JakartaEE interceptor API but adapted to OSGi services.
+
+It enables to proxy any service and execute code around the service methods.
+
+== Dependencies
+
+[source,xml]
+----
+<dependency>
+  <groupId>org.apache.karaf.services</groupId>
+  <artifactId>org.apache.karaf.services.interceptor.api</artifactId>
+</dependency>
+----
+
+== Defining an interceptor
+
+An interceptor is simply an OSGi service marked with `@Interceptor` and having an interceptor binding which is nothing more than an annotation marked with `@InterceptorBinding`.
+
+Here is a binding:
+
+[source,java]
+----
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+@InterceptorBinding
+public @interface Suffix {
+}
+----
+
+And here is an associated interceptor:
+
+[source,java]
+----
+@Suffix
+@Interceptor
+@Component(service = SuffixingInterceptor.class)
+public class SuffixingInterceptor {
+    // ...
+}
+----
+
+TIP: the examples are using SCR but there is no requirement to do so, you can do it with `context.registerService()` as well.
+
+For the interceptor to do something, you must define an `@AroundInvoke` method which will intercept method calls in intercepted services.
+It must takes a single parameter of type `InvocationContext`:
+
+[source,java]
+----
+@AroundInvoke
+public Object around(final InvocationContext context) throws Exception {
+    return context.proceed() + "(suffixed)";
+}
+----
+
+== Using interceptors
+
+Assuming you have an interceptor library (it is commong for transversal concerns like security, auditing, tracing, metrics, etc...), you can enable the interceptor usages with these few steps:
+
+. Ensure you register your service as an OSGi service,
+. Mark the service with `@EnableInterceptors`,
+. Mark the class or method with the interceptor bindings you want
+
+TIP: if you put a binding on a class is it available for all methods and is called after method level interceptors.
+
+As an example speaks better than 1000 words, here is a service using our previous suffixing interceptor:
+
+[source,java]
+----
+@EnableInterceptors
+@Component(service = InterceptedService.class)
+public class InterceptedService {
+    @Suffix
+    public String doStuff(final String value) {
+        return "'" + value + "'";
+    }
+}
+----
+
+You can notice that it is equivalent to the following example which just moved the interceptor at class level:
+
+
+[source,java]
+----
+@Suffix
+@EnableInterceptors
+@Component(service = InterceptedService.class)
+public class InterceptedService {
+    public String doStuff(final String value) {
+        return "'" + value + "'";
+    }
+}
+----
+
+== Proxying implementation
+
+If possible, the proxying will use `java.lang.reflect.Proxy` but if there is a class to proxy and not only interfaces, `asm` must be available for the proxy to suceed to be created.
diff --git a/pom.xml b/pom.xml
index e2e6ef1..59bb169 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
     <parent>
         <groupId>org.apache</groupId>
         <artifactId>apache</artifactId>
-        <version>21</version>
+        <version>23</version>
         <relativePath />
     </parent>
 
@@ -150,23 +150,23 @@
         <camel.version>2.24.3</camel.version>
         <cglib.bundle.version>3.2.9_1</cglib.bundle.version>
         <cxf.version>3.3.5</cxf.version>
-        <jackson.version>2.10.2</jackson.version>
-        <jna.version>5.5.0</jna.version>
-        <jaxb.version>2.3.2</jaxb.version> 
+        <jackson.version>2.10.4</jackson.version>
+        <jna.version>5.6.0</jna.version>
+        <jaxb.version>2.3.3</jaxb.version> 
         <commons-beanutils.version>1.9.4</commons-beanutils.version>
-        <commons-codec.version>1.13</commons-codec.version>
+        <commons-codec.version>1.14</commons-codec.version>
         <commons-compress.version>1.20</commons-compress.version>
         <commons-collections.version>3.2.2</commons-collections.version>
         <commons-dbcp.version>1.4_3</commons-dbcp.version>
         <commons-fileupload.version>1.4</commons-fileupload.version>
         <commons-lang.version>2.6</commons-lang.version>
-        <commons-lang3.version>3.10</commons-lang3.version>
+        <commons-lang3.version>3.11</commons-lang3.version>
         <commons-logging.version>1.2</commons-logging.version>
         <commons-pool.version>1.6</commons-pool.version>
         <commons-pool2.version>2.6.2</commons-pool2.version>
-        <commons-io.version>2.6</commons-io.version>
+        <commons-io.version>2.7</commons-io.version>
         <dom4j.bundle.version>1.6.1_5</dom4j.bundle.version>
-        <eclipselink.version>2.7.5</eclipselink.version>
+        <eclipselink.version>2.7.7</eclipselink.version>
         <jasypt.bundle.version>1.9.3_1</jasypt.bundle.version>
         <jolokia.version>1.6.2</jolokia.version>
         <serp.bundle.version>1.15.1_1</serp.bundle.version>
@@ -183,33 +183,33 @@
         <easymock.version>4.2</easymock.version>
         <equinox.groupId>org.eclipse.platform</equinox.groupId>
         <equinox.artifactId>org.eclipse.osgi</equinox.artifactId>
-        <equinox.version>3.15.100</equinox.version>
+        <equinox.version>3.15.300</equinox.version>
         <bndlib.version>3.5.0</bndlib.version>
         <equinox.region.version>1.2.101.v20150831-1342</equinox.region.version>
         <equinox.coordinator.version>1.1.0.v20120522-1841</equinox.coordinator.version>
 
         <felix.bundlerepository.version>2.0.10</felix.bundlerepository.version>
-        <felix.configadmin.version>1.9.16</felix.configadmin.version>
+        <felix.configadmin.version>1.9.18</felix.configadmin.version>
         <felix.connect.version>0.2.0</felix.connect.version>
         <felix.coordinator.version>1.0.2</felix.coordinator.version>
-        <felix.fileinstall.version>3.6.4</felix.fileinstall.version>
+        <felix.fileinstall.version>3.6.8</felix.fileinstall.version>
         <felix.framework.version>6.0.3</felix.framework.version>
         <felix.framework.security.version>2.6.1</felix.framework.security.version>
         <felix.gogo.runtime.version>1.1.2</felix.gogo.runtime.version>
-        <felix.gogo.jline.version>1.1.4</felix.gogo.jline.version>
+        <felix.gogo.jline.version>1.1.6</felix.gogo.jline.version>
         <felix.httplite.version>0.1.6</felix.httplite.version>
-        <felix.http.version>4.0.16</felix.http.version>
+        <felix.http.version>4.0.20</felix.http.version>
         <felix.inventory.version>1.0.6</felix.inventory.version>
-        <felix.plugin.version>4.2.1</felix.plugin.version>
+        <felix.plugin.version>5.1.1</felix.plugin.version>
         <felix.utils.version>1.11.2</felix.utils.version>
-        <felix.webconsole.version>4.3.16</felix.webconsole.version>
-        <felix.webconsole.api.version>3.1.2</felix.webconsole.api.version>
+        <felix.webconsole.version>4.5.4</felix.webconsole.version>
+        <felix.webconsole.api.version>3.3.0</felix.webconsole.api.version>
         <felix.memoryusage.webconsole.plugin.version>1.0.10</felix.memoryusage.webconsole.plugin.version>
         <felix.metatype.version>1.2.2</felix.metatype.version>
         <felix.eventadmin.version>1.5.0</felix.eventadmin.version>
         <felix.eventadmin.webconsole.plugin.version>1.1.8</felix.eventadmin.webconsole.plugin.version>
         <felix.obr.version>1.0.2</felix.obr.version>
-        <felix.scr.version>2.1.16</felix.scr.version>
+        <felix.scr.version>2.1.20</felix.scr.version>
         <felix.scr.webconsole.plugin.version>2.1.0</felix.scr.webconsole.plugin.version>
         <felix.scr.annotation.version>1.12.0</felix.scr.annotation.version>
         <felix.resolver.version>2.0.0</felix.resolver.version>
@@ -237,9 +237,9 @@
         <aries.jndi.api.version>1.1.0</aries.jndi.api.version>
         <aries.jndi.core.version>1.0.2</aries.jndi.core.version>
         <aries.jndi.url.version>1.1.0</aries.jndi.url.version>
-        <aries.proxy.version>1.1.6</aries.proxy.version>
-        <aries.proxy.api.version>1.1.0</aries.proxy.api.version>
-        <aries.spifly.version>1.2.3</aries.spifly.version>
+        <aries.proxy.version>1.1.8</aries.proxy.version>
+        <aries.proxy.api.version>1.1.1</aries.proxy.api.version>
+        <aries.spifly.version>1.2.4</aries.spifly.version>
         <aries.subsystem.api.version>2.0.10</aries.subsystem.api.version>
         <aries.subsystem.core.version>2.0.10</aries.subsystem.core.version>
         <aries.transaction.manager.version>1.3.3</aries.transaction.manager.version>
@@ -248,10 +248,10 @@
         <aries.util.version>1.1.3</aries.util.version>
         <atomikos.version>4.0.4</atomikos.version>
 
-        <openjpa.version>3.0.0</openjpa.version>
+        <openjpa.version>3.1.1</openjpa.version>
         <geronimo.transaction.manager.version>3.1.3</geronimo.transaction.manager.version>
         <guava.version>20.0</guava.version>
-        <narayana.version>5.10.4.Final</narayana.version>
+        <narayana.version>5.10.5.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>
@@ -259,13 +259,13 @@
         <hibernate42.version>4.2.15.Final</hibernate42.version>
         <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.1.0.Final</hibernate.validator.version>
+        <hibernate.version>5.4.17.Final</hibernate.version>
+        <hibernate.validator.version>6.1.5.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>
-        <jetty.version>9.4.22.v20191022</jetty.version>
-        <jline.version>3.14.1</jline.version>
+        <jetty.version>9.4.30.v20200611</jetty.version>
+        <jline.version>3.16.0</jline.version>
         <junit.version>4.13</junit.version>
         <jsw.version>3.2.3</jsw.version>
         <log4j.version>1.2.17</log4j.version>
@@ -276,18 +276,18 @@
         <org.osgi.service.jdbc.version>1.0.0</org.osgi.service.jdbc.version>
         <org.osgi.service.jpa.version>1.0.0</org.osgi.service.jpa.version>
         <osgi.version>7.0.0</osgi.version>
-        <osgi.compendium.version>6.0.0</osgi.compendium.version>
+        <osgi.compendium.version>7.0.0</osgi.compendium.version>
 
         <pax.cdi.version>1.1.3</pax.cdi.version>
         <pax.exam.version>4.13.3</pax.exam.version>
-        <pax.logging.version>2.0.2</pax.logging.version>
+        <pax.logging.version>2.0.5</pax.logging.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.14</pax.web.version>
+        <pax.web.version>7.2.18</pax.web.version>
         <pax.tinybundle.version>3.0.0</pax.tinybundle.version>
         <pax.jdbc.version>1.4.4</pax.jdbc.version>
-        <pax.jms.version>1.0.6</pax.jms.version>
+        <pax.jms.version>1.0.7</pax.jms.version>
         <pax.transx.version>0.4.4</pax.transx.version>
 
         <portlet-api.version>2.0</portlet-api.version>
@@ -301,17 +301,18 @@
         <spring42.version>4.2.9.RELEASE_1</spring42.version>
         <spring43.version>4.3.25.RELEASE_1</spring43.version>
         <spring50.version>5.0.15.RELEASE_1</spring50.version>
-        <spring51.version>5.1.9.RELEASE_1</spring51.version>
-        <spring52.version>5.2.2.RELEASE_1</spring52.version>
+        <spring51.version>5.1.14.RELEASE_1</spring51.version>
+        <spring52.version>5.2.5.RELEASE_1</spring52.version>
         <spring.security31.version>3.1.4.RELEASE</spring.security31.version>
         <spring.security42.version>4.2.4.RELEASE_1</spring.security42.version>
         <spring.security51.version>5.1.5.RELEASE_1</spring.security51.version>
+        <spring.security53.version>5.3.1.RELEASE_1</spring.security53.version>
 
-        <sshd.version>1.7.0</sshd.version>
+        <sshd.version>2.5.1</sshd.version>
         <derby-version>10.14.2.0</derby-version>
         <directory-version>2.0.0-M24</directory-version>
         <struts.bundle.version>1.3.10_1</struts.bundle.version>
-        <xbean.version>4.16</xbean.version>
+        <xbean.version>4.17</xbean.version>
         <xerces.bundle.version>2.12.0_1</xerces.bundle.version>
         <xalan.bundle.version>2.7.2_3</xalan.bundle.version>
         <xalan-serializer.bundle.version>2.7.2_1</xalan-serializer.bundle.version>
@@ -320,6 +321,7 @@
         <websocket.version>1.1</websocket.version>
         <winsw.version>2.3.0</winsw.version>
 
+        <osgi-component-annotations.version>1.4.0</osgi-component-annotations.version>
 
         <surefire.argLine />
 
@@ -1410,7 +1412,17 @@
             </dependency>
             <dependency>
                 <groupId>org.apache.sshd</groupId>
-                <artifactId>sshd-core</artifactId>
+                <artifactId>sshd-osgi</artifactId>
+                <version>${sshd.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.sshd</groupId>
+                <artifactId>sshd-scp</artifactId>
+                <version>${sshd.version}</version>
+            </dependency>
+            <dependency>
+                <groupId>org.apache.sshd</groupId>
+                <artifactId>sshd-sftp</artifactId>
                 <version>${sshd.version}</version>
             </dependency>
             <dependency>
@@ -1773,7 +1785,7 @@
             <dependency>
                 <groupId>org.apache.geronimo.specs</groupId>
                 <artifactId>geronimo-atinject_1.0_spec</artifactId>
-                <version>1.1</version>
+                <version>1.2</version>
             </dependency>
             <dependency>
                 <groupId>jakarta.xml.bind</groupId>
@@ -1843,7 +1855,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-javadoc-plugin</artifactId>
-                    <version>3.1.1</version>
+                    <version>3.2.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1853,7 +1865,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-remote-resources-plugin</artifactId>
-                    <version>1.6.0</version>
+                    <version>1.7.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1913,7 +1925,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-source-plugin</artifactId>
-                    <version>3.2.0</version>
+                    <version>3.2.1</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -1923,7 +1935,7 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-antrun-plugin</artifactId>
-                    <version>1.8</version>
+                    <version>3.0.0</version>
                     <dependencies>
                         <dependency>
                             <groupId>ant-contrib</groupId>
@@ -1961,17 +1973,17 @@
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-dependency-plugin</artifactId>
-                    <version>3.1.1</version>
+                    <version>3.1.2</version>
                 </plugin>
                 <plugin>
                     <groupId>org.codehaus.mojo</groupId>
                     <artifactId>build-helper-maven-plugin</artifactId>
-                    <version>3.0.0</version>
+                    <version>3.1.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
                     <artifactId>maven-site-plugin</artifactId>
-                    <version>3.8.2</version>
+                    <version>3.9.0</version>
                 </plugin>
                 <plugin>
                     <groupId>org.apache.maven.plugins</groupId>
@@ -2200,7 +2212,7 @@
                     <plugin>
                         <groupId>org.apache.rat</groupId>
                         <artifactId>apache-rat-plugin</artifactId>
-                        <version>0.12</version>
+                        <version>0.13</version>
                         <executions>
                             <execution>
                                 <phase>verify</phase>
@@ -2285,6 +2297,12 @@
                                 <goals>
                                     <goal>sign</goal>
                                 </goals>
+                                <configuration>
+                                  <gpgArguments>
+                                    <arg>--pinentry-mode</arg>
+                                    <arg>loopback</arg>
+                                  </gpgArguments>
+                                </configuration>
                             </execution>
                         </executions>
                     </plugin>
diff --git a/scheduler/pom.xml b/scheduler/pom.xml
index 3f27603..5ce7a31 100644
--- a/scheduler/pom.xml
+++ b/scheduler/pom.xml
@@ -140,7 +140,7 @@
 	        <id>java9-plus</id>
 	        <properties>
                 <plexus-utils.version>3.1.0</plexus-utils.version>
-                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
+                <mvn.opts>--add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED -Djava.io.tmpdir=${project.build.directory}</mvn.opts>
 	        </properties>
                 <dependencies>
                     <dependency>
diff --git a/scr/state/pom.xml b/scr/state/pom.xml
index 265aef0..cf4348d 100644
--- a/scr/state/pom.xml
+++ b/scr/state/pom.xml
@@ -62,7 +62,7 @@
         <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.component.annotations</artifactId>
-            <version>1.3.0</version>
+            <version>${osgi-component-annotations.version}</version>
         </dependency>
         <dependency>
             <groupId>org.apache.felix</groupId>
diff --git a/services/interceptor/api/pom.xml b/services/interceptor/api/pom.xml
new file mode 100644
index 0000000..e8e6dda
--- /dev/null
+++ b/services/interceptor/api/pom.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <!--
+
+      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.
+  -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.services</groupId>
+        <artifactId>org.apache.karaf.services.interceptor</artifactId>
+        <version>4.3.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.services.interceptor.api</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Services :: Interceptor :: API</name>
+    <description>Interceptor API.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>${osgi-component-annotations.version}</version>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                  <instructions>
+                    <Export-Package>org.apache.karaf.service.interceptor.api</Export-Package>
+                  </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/AroundInvoke.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/AroundInvoke.java
new file mode 100644
index 0000000..a2e7306
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/AroundInvoke.java
@@ -0,0 +1,40 @@
+/*
+ * 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.service.interceptor.api;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Marks an interceptor public method as the intercepting one.
+ *
+ * It must be:
+ * <ul>
+ *     <li>public</li>
+ *     <li>have a single {@link InvocationContext} parameter</li>
+ * </ul>
+ *
+ * It can optionally throw {@link Exception}.
+ */
+@Target(METHOD)
+@Retention(RUNTIME) // no support of methods in SCR so we use plain reflection
+@RequireInterceptorImpl
+public @interface AroundInvoke {
+}
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/EnableInterceptors.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/EnableInterceptors.java
new file mode 100644
index 0000000..c07b09e
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/EnableInterceptors.java
@@ -0,0 +1,34 @@
+/*
+ * 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.service.interceptor.api;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.osgi.service.component.annotations.ComponentPropertyType;
+
+// note: would be better to make it a stereotype but bnd does not support it yet so let's make it a wrapper
+@Target(TYPE)
+@Retention(CLASS)
+@ComponentPropertyType
+@RequireInterceptorImpl
+public @interface EnableInterceptors {
+    String PREFIX_ = "apache.karaf."; // we don't want just "interceptor" as property key
+}
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/Interceptor.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/Interceptor.java
new file mode 100644
index 0000000..c3c5993
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/Interceptor.java
@@ -0,0 +1,33 @@
+/*
+ * 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.service.interceptor.api;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.osgi.service.component.annotations.ComponentPropertyType;
+
+@Target(TYPE)
+@Retention(CLASS)
+@ComponentPropertyType
+@RequireInterceptorImpl
+public @interface Interceptor {
+    String PREFIX_ = "apache.karaf."; // we don't want just "interceptor" as property key
+}
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InterceptorBinding.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InterceptorBinding.java
new file mode 100644
index 0000000..d084451
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InterceptorBinding.java
@@ -0,0 +1,28 @@
+/*
+ * 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.service.interceptor.api;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(RUNTIME)
+@Target(ANNOTATION_TYPE)
+public @interface InterceptorBinding {
+}
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InvocationContext.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InvocationContext.java
new file mode 100644
index 0000000..a157726
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/InvocationContext.java
@@ -0,0 +1,34 @@
+/*
+ * 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.service.interceptor.api;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+public interface InvocationContext {
+    Object getTarget();
+
+    Method getMethod();
+
+    Object[] getParameters();
+
+    void setParameters(Object[] var1);
+
+    Map<String, Object> getContextData();
+
+    Object proceed() throws Exception;
+}
\ No newline at end of file
diff --git a/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/RequireInterceptorImpl.java b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/RequireInterceptorImpl.java
new file mode 100644
index 0000000..25b4ef1
--- /dev/null
+++ b/services/interceptor/api/src/main/java/org/apache/karaf/service/interceptor/api/RequireInterceptorImpl.java
@@ -0,0 +1,35 @@
+/*
+ * 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.service.interceptor.api;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(CLASS)
+@Target(TYPE)
+/* when on r7
+@Requirement(
+    namespace = "osgi.implementation",
+    name = "org.apache.karaf.service.interceptor.impl",
+    version = "${project.version}" // java-template plugin?
+)
+*/
+@interface RequireInterceptorImpl {
+}
\ No newline at end of file
diff --git a/services/interceptor/impl/pom.xml b/services/interceptor/impl/pom.xml
new file mode 100644
index 0000000..f5a6bf8
--- /dev/null
+++ b/services/interceptor/impl/pom.xml
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <!--
+
+      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.
+  -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.services</groupId>
+        <artifactId>org.apache.karaf.services.interceptor</artifactId>
+        <version>4.3.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.services.interceptor.impl</artifactId>
+    <packaging>bundle</packaging>
+    <name>Apache Karaf :: Services :: Interceptor :: Implementation</name>
+    <description>Interceptor implementation.</description>
+
+    <dependencies>
+        <dependency>
+            <groupId>${project.groupId}</groupId>
+            <artifactId>org.apache.karaf.services.interceptor.api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>osgi.core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ow2.asm</groupId>
+            <artifactId>asm</artifactId>
+            <version>${asm.version}</version>
+            <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <version>${osgi-component-annotations.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-junit4</artifactId>
+            <version>${pax.exam.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.felix</groupId>
+                    <artifactId>org.apache.felix.configadmin</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.ops4j.pax.exam</groupId>
+            <artifactId>pax-exam-container-karaf</artifactId>
+            <version>${pax.exam.version}</version>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.felix</groupId>
+                    <artifactId>org.apache.felix.configadmin</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf</groupId>
+            <artifactId>apache-karaf</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+            <type>tar.gz</type>
+            <exclusions>
+                <exclusion>
+                    <groupId>*</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-atinject_1.0_spec</artifactId>
+            <version>1.2</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-jdk14</artifactId>
+            <version>${slf4j.version}</version>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.karaf.specs</groupId>
+            <artifactId>org.apache.karaf.specs.locator</artifactId>
+            <version>${project.version}</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>java9-plus</id>
+            <activation>
+                <jdk>[9,)</jdk>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>jakarta.xml.bind</groupId>
+                    <artifactId>jakarta.xml.bind-api</artifactId>
+                </dependency>
+                <dependency>
+                    <groupId>org.glassfish.jaxb</groupId>
+                    <artifactId>jaxb-runtime</artifactId>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+
+    <build>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <version>3.1.1</version>
+                <executions>
+                    <execution>
+                        <id>copy</id>
+                        <phase>generate-test-resources</phase>
+                        <goals>
+                            <goal>copy</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <artifactItems>
+                        <artifactItem>
+                            <groupId>org.ow2.asm</groupId>
+                            <artifactId>asm</artifactId>
+                            <version>${asm.version}</version>
+                            <type>jar</type>
+                            <overWrite>true</overWrite>
+                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
+                            <destFileName>asm.jar</destFileName>
+                        </artifactItem>
+                        <artifactItem>
+                            <groupId>org.apache.karaf</groupId>
+                            <artifactId>apache-karaf</artifactId>
+                            <version>${project.version}</version>
+                            <type>tar.gz</type>
+                            <overWrite>true</overWrite>
+                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
+                            <destFileName>karaf.tar.gz</destFileName>
+                        </artifactItem>
+                    </artifactItems>
+                    <overWriteReleases>false</overWriteReleases>
+                    <overWriteSnapshots>true</overWriteSnapshots>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <configuration>
+                    <instructions>
+                        <Bundle-Activator>org.apache.karaf.service.interceptor.impl.activator.InterceptorActivator</Bundle-Activator>
+                        <Export-Package>org.apache.karaf.service.interceptor.impl.activator</Export-Package>
+                        <Private-Package>
+                          org.apache.karaf.service.interceptor.impl.runtime,
+                          org.apache.karaf.service.interceptor.impl.runtime.hook,
+                          org.apache.karaf.service.interceptor.impl.runtime.invoker,
+                          org.apache.karaf.service.interceptor.impl.runtime.proxy,
+                          org.apache.karaf.service.interceptor.impl.runtime.registry
+                        </Private-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-surefire-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>default-test</id>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <excludes>
+                                <exclude>**/E2E*</exclude>
+                            </excludes>
+                        </configuration>
+                    </execution>
+                    <execution>
+                        <id>e2e</id>
+                        <phase>package</phase>
+                        <goals>
+                            <goal>test</goal>
+                        </goals>
+                        <configuration>
+                            <includes>
+                                <include>**/E2E*</include>
+                            </includes>
+                        </configuration>
+                    </execution>
+                </executions>
+                <configuration>
+                    <trimStackTrace>false</trimStackTrace>
+                    <systemPropertyVariables>
+                        <java.util.logging.SimpleFormatter.format>%1$tF %1$tT [%4$s] [%2$-89s] %5$s%6$s%n</java.util.logging.SimpleFormatter.format>
+                        <karaf.version>${project.version}</karaf.version>
+                    </systemPropertyVariables>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/activator/InterceptorActivator.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/activator/InterceptorActivator.java
new file mode 100644
index 0000000..8fd01ba
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/activator/InterceptorActivator.java
@@ -0,0 +1,96 @@
+/*
+ * 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.service.interceptor.impl.activator;
+
+import static java.util.Optional.ofNullable;
+import static org.apache.karaf.service.interceptor.impl.runtime.ComponentProperties.INTERCEPTORS_PROPERTY;
+import static org.apache.karaf.service.interceptor.impl.runtime.ComponentProperties.INTERCEPTOR_PROPERTY;
+
+import java.util.Hashtable;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.impl.runtime.PropertiesManager;
+import org.apache.karaf.service.interceptor.impl.runtime.ProxiesManager;
+import org.apache.karaf.service.interceptor.impl.runtime.hook.InterceptedInstancesHooks;
+import org.apache.karaf.service.interceptor.impl.runtime.proxy.ProxyFactory;
+import org.apache.karaf.service.interceptor.impl.runtime.registry.InterceptedServiceRegistry;
+import org.apache.karaf.service.interceptor.impl.runtime.registry.InterceptorRegistry;
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+
+public class InterceptorActivator implements BundleActivator {
+    private InterceptorRegistry interceptorRegistry;
+    private InterceptedServiceRegistry interceptedServiceRegistry;
+    private ProxiesManager proxiesManager;
+
+    private ServiceRegistration<?> hooksRegistration;
+
+    @Override
+    public void start(final BundleContext context) throws InvalidSyntaxException {
+        final PropertiesManager propertiesManager = new PropertiesManager();
+        // todo: decouple these three services with a bus? here we use the activator to keep it simple
+        interceptedServiceRegistry = new InterceptedServiceRegistry(this::onServiceAddition, this::onServiceRemoval, propertiesManager);
+        interceptorRegistry = new InterceptorRegistry(this::onInterceptorAddition, this::onInterceptorRemoval, propertiesManager);
+        proxiesManager = new ProxiesManager(interceptorRegistry, interceptedServiceRegistry, new ProxyFactory(), propertiesManager);
+
+        // listen for interceptors and intercepted instances to be able to react on (un)registrations
+        context.addServiceListener(interceptedServiceRegistry, "(" + INTERCEPTORS_PROPERTY + "=true)");
+        context.addServiceListener(interceptorRegistry, "(" + INTERCEPTOR_PROPERTY + "=true)");
+
+        // register existing services/interceptors
+        ofNullable(context.getAllServiceReferences(null, "(" + INTERCEPTORS_PROPERTY + "=true)"))
+                .ifPresent(refs -> Stream.of(refs).forEach(ref -> interceptedServiceRegistry.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, ref))));
+        ofNullable(context.getAllServiceReferences(null, "(" + INTERCEPTOR_PROPERTY + "=true)"))
+                .ifPresent(refs -> Stream.of(refs).forEach(ref -> interceptorRegistry.serviceChanged(new ServiceEvent(ServiceEvent.REGISTERED, ref))));
+
+        // ensure we filter out the proxied services to only return proxies
+        hooksRegistration = context.registerService(
+                new String[]{FindHook.class.getName(), EventListenerHook.class.getName()},
+                new InterceptedInstancesHooks(context.getBundle().getBundleId()),
+                new Hashtable<>());
+    }
+
+    @Override
+    public void stop(final BundleContext context) {
+        context.removeServiceListener(interceptorRegistry);
+        context.removeServiceListener(interceptedServiceRegistry);
+        hooksRegistration.unregister();
+        proxiesManager.stop();
+    }
+
+    private void onServiceAddition(final ServiceReference<?> ref) {
+        proxiesManager.onInterceptedInstanceAddition(ref);
+    }
+
+    private void onServiceRemoval(final ServiceReference<?> ref) {
+        proxiesManager.onInterceptedInstanceRemoval(ref);
+    }
+
+    private void onInterceptorAddition(final Class<?> aClass) {
+        proxiesManager.onInterceptorAddition(aClass);
+    }
+
+    private void onInterceptorRemoval(final Class<?> aClass) {
+        proxiesManager.onInterceptorRemoval(aClass);
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ComponentProperties.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ComponentProperties.java
new file mode 100644
index 0000000..ff842be
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ComponentProperties.java
@@ -0,0 +1,22 @@
+/*
+ * 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.service.interceptor.impl.runtime;
+
+public interface ComponentProperties {
+    String INTERCEPTORS_PROPERTY = "apache.karaf.enable.interceptors";
+    String INTERCEPTOR_PROPERTY = "apache.karaf.interceptor";
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/Exceptions.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/Exceptions.java
new file mode 100644
index 0000000..37fa376
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/Exceptions.java
@@ -0,0 +1,36 @@
+/*
+ * 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.service.interceptor.impl.runtime;
+
+import java.lang.reflect.InvocationTargetException;
+
+public final class Exceptions {
+    private Exceptions() {
+        // no-op
+    }
+
+    public static Object unwrap(final InvocationTargetException ite) throws Exception {
+        final Throwable targetException = ite.getTargetException();
+        if (Exception.class.isInstance(targetException)) {
+            throw Exception.class.cast(targetException);
+        }
+        if (Error.class.isInstance(targetException)) {
+            throw Error.class.cast(targetException);
+        }
+        throw ite; // quite unlikely
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/PropertiesManager.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/PropertiesManager.java
new file mode 100644
index 0000000..f610cec
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/PropertiesManager.java
@@ -0,0 +1,38 @@
+/*
+ * 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.service.interceptor.impl.runtime;
+
+import java.util.Hashtable;
+import java.util.stream.Collector;
+import java.util.stream.Stream;
+
+import org.osgi.framework.ServiceReference;
+
+public class PropertiesManager {
+    public Stream<String> unflattenStringValues(final Object it) {
+        return String[].class.isInstance(it) ? Stream.of(String[].class.cast(it)) : Stream.of(String.class.cast(it));
+    }
+
+    public <T> Hashtable<String, Object> collectProperties(final ServiceReference<T> ref) {
+        return Stream.of(ref.getPropertyKeys())
+                .filter(it -> !ComponentProperties.INTERCEPTORS_PROPERTY.equals(it))
+                .collect(Collector.of(Hashtable::new, (h, p) -> h.put(p, ref.getProperty(p)), (p1, p2) -> {
+                    p1.putAll(p2);
+                    return p1;
+                }));
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ProxiesManager.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ProxiesManager.java
new file mode 100644
index 0000000..efa5e46
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/ProxiesManager.java
@@ -0,0 +1,133 @@
+/*
+ * 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.service.interceptor.impl.runtime;
+
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.toList;
+
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.impl.runtime.proxy.ProxyFactory;
+import org.apache.karaf.service.interceptor.impl.runtime.registry.InterceptedServiceRegistry;
+import org.apache.karaf.service.interceptor.impl.runtime.registry.InterceptorRegistry;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+
+public class ProxiesManager {
+    private final ProxyFactory proxyFactory;
+    private final PropertiesManager propertiesManager;
+    private final InterceptorRegistry interceptors;
+    private final InterceptedServiceRegistry services;
+
+    private final Map<ServiceReference<?>, ServiceRegistration<?>> registrationPerReference = new ConcurrentHashMap<>();
+    private final Map<ServiceReference<?>, List<Class<?>>> bindingPerReference = new ConcurrentHashMap<>();
+    private final Map<Class<?>, Collection<ServiceReference<?>>> referencesPerBinding = new ConcurrentHashMap<>();
+
+    public ProxiesManager(final InterceptorRegistry interceptorRegistry,
+                          final InterceptedServiceRegistry services,
+                          final ProxyFactory proxyFactory,
+                          final PropertiesManager propertiesManager) {
+        this.interceptors = interceptorRegistry;
+        this.services = services;
+        this.proxyFactory = proxyFactory;
+        this.propertiesManager = propertiesManager;
+    }
+
+    // check out all services not yet proxied which can now be proxied and register the proxy
+    public void onInterceptorAddition(final Class<?> bindingClass) {
+        ofNullable(referencesPerBinding.get(bindingClass))
+                .ifPresent(references -> references.stream()
+                        .filter(ref -> !registrationPerReference.containsKey(ref)) // already proxied so skip
+                        .filter(ref -> ofNullable(bindingPerReference.get(ref))
+                                .map(b -> interceptors.areBindingsAvailable(b.stream()))
+                                .orElse(false))
+                        .forEach(ref -> registrationPerReference.put(ref, registerProxy(ref))));
+    }
+
+    // remove registered proxies since one of the interceptor is no more available
+    public void onInterceptorRemoval(final Class<?> bindingClass) {
+        ofNullable(referencesPerBinding.get(bindingClass))
+                .ifPresent(references -> references.stream()
+                        .filter(registrationPerReference::containsKey)
+                        .forEach(ref -> ofNullable(registrationPerReference.remove(ref))
+                                .ifPresent(ServiceRegistration::unregister)));
+    }
+
+    public <T> void onInterceptedInstanceAddition(final ServiceReference<T> ref) {
+        final List<Class<?>> bindings = toBindings(ref).collect(toList());
+        bindings.forEach(binding -> referencesPerBinding.computeIfAbsent(binding, k -> new CopyOnWriteArraySet<>()).add(ref));
+        bindingPerReference.put(ref, bindings);
+        if (interceptors.areBindingsAvailable(bindings.stream())) {
+            registrationPerReference.put(ref, registerProxy(ref));
+        }
+    }
+
+    public <T> void onInterceptedInstanceRemoval(final ServiceReference<T> ref) {
+        toBindings(ref).filter(referencesPerBinding::containsKey).forEach(binding -> {
+            final Collection<ServiceReference<?>> refs = referencesPerBinding.get(binding);
+            refs.remove(ref);
+            if (refs.isEmpty()) {
+                referencesPerBinding.remove(binding);
+            }
+        });
+        bindingPerReference.remove(ref);
+        ofNullable(registrationPerReference.remove(ref))
+                .ifPresent(ServiceRegistration::unregister);
+    }
+
+    private <T> Stream<? extends Class<?>> toBindings(final ServiceReference<T> ref) {
+        return services.getBindings(ref);
+    }
+
+    private <T> ServiceRegistration<?> registerProxy(final ServiceReference<T> ref) {
+        final BundleContext context = ref.getBundle().getBundleContext();
+        final Object classProperty = ref.getProperty(Constants.OBJECTCLASS);
+        final List<Class<?>> classes = Stream.of(classProperty)
+                .flatMap(propertiesManager::unflattenStringValues)
+                .map(it -> {
+                    try {
+                        return context.getBundle().loadClass(it);
+                    } catch (final ClassNotFoundException e) {
+                        throw new IllegalStateException(e);
+                    }
+                })
+                .collect(toList());
+
+        // drop interceptors property to let it be forwarded
+        final Hashtable<String, Object> properties = propertiesManager.collectProperties(ref);
+        final T proxy = proxyFactory.create(
+                ref, classes,
+                interceptors.getInterceptors(bindingPerReference.get(ref)),
+                services.getInterceptorsPerMethod(ref));
+        return context.registerService(classes.stream().map(Class::getName).toArray(String[]::new), proxy, properties);
+    }
+
+    public void stop() {
+        registrationPerReference.values().forEach(ServiceRegistration::unregister);
+        bindingPerReference.clear();
+        referencesPerBinding.clear();
+        registrationPerReference.clear();
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptedInstancesHooks.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptedInstancesHooks.java
new file mode 100644
index 0000000..55d4a57
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptedInstancesHooks.java
@@ -0,0 +1,63 @@
+/*
+ * 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.service.interceptor.impl.runtime.hook;
+
+import static org.apache.karaf.service.interceptor.impl.runtime.ComponentProperties.INTERCEPTORS_PROPERTY;
+
+import java.util.Collection;
+import java.util.Map;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.hooks.service.EventListenerHook;
+import org.osgi.framework.hooks.service.FindHook;
+import org.osgi.framework.hooks.service.ListenerHook;
+
+public class InterceptedInstancesHooks implements FindHook, EventListenerHook {
+    private final long bundleId;
+
+    public InterceptedInstancesHooks(final long bundleId) {
+        this.bundleId = bundleId;
+    }
+
+    // replaced services are not forward to listeners except the bundle owning the replacer and #0 (optional for the test)
+    @Override
+    public void event(final ServiceEvent event, final Map<BundleContext, Collection<ListenerHook.ListenerInfo>> listeners) {
+        if (isIntercepted(event.getServiceReference())) {
+            listeners.keySet().removeIf(this::isNeitherFrameworkNorSelf);
+        }
+    }
+
+    // remove replaced services to keep only replacements
+    @Override
+    public void find(final BundleContext context, final String name, final String filter,
+                     final boolean allServices, final Collection<ServiceReference<?>> references) {
+        if (isNeitherFrameworkNorSelf(context)) {
+            references.removeIf(this::isIntercepted);
+        }
+    }
+
+    private boolean isNeitherFrameworkNorSelf(final BundleContext b) {
+        final long id = b.getBundle().getBundleId();
+        return id != 0 && id != bundleId;
+    }
+
+    private boolean isIntercepted(final ServiceReference<?> serviceReference) {
+        return Boolean.parseBoolean(String.valueOf(serviceReference.getProperty(INTERCEPTORS_PROPERTY)));
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptorInstance.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptorInstance.java
new file mode 100644
index 0000000..204b3e2
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/hook/InterceptorInstance.java
@@ -0,0 +1,89 @@
+/*
+ * 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.service.interceptor.impl.runtime.hook;
+
+import static java.util.stream.Collectors.toList;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.api.AroundInvoke;
+import org.apache.karaf.service.interceptor.api.InvocationContext;
+import org.apache.karaf.service.interceptor.impl.runtime.Exceptions;
+import org.apache.karaf.service.interceptor.impl.runtime.PropertiesManager;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class InterceptorInstance<T> {
+    private final ServiceReference<T> reference;
+    private final BundleContext context;
+    private final Method method;
+    private final Class<?> binding;
+
+    public InterceptorInstance(final ServiceReference<T> reference, final Class<?> binding, final PropertiesManager propertiesManager) {
+        this.reference = reference;
+        this.context = reference.getBundle().getBundleContext();
+        this.method = propertiesManager.unflattenStringValues(reference.getProperty(Constants.OBJECTCLASS))
+            .map(this::findAroundInvoke)
+            .filter(Objects::nonNull)
+            .findFirst()
+            .orElse(null);
+        this.binding = binding;
+    }
+
+    public Class<?> getBinding() {
+        return binding;
+    }
+
+    public Object intercept(final InvocationContext invocationContext) throws Exception {
+        final T service = context.getService(reference);
+        if (service == null) {
+            throw new IllegalStateException("'" + reference + "' no more available");
+        }
+        try {
+            return method == null ? invocationContext.proceed() : method.invoke(service, invocationContext);
+        } catch (final InvocationTargetException ite) {
+            return Exceptions.unwrap(ite);
+        } finally {
+            context.ungetService(reference);
+        }
+    }
+
+    private Method findAroundInvoke(final String clazz) {
+        try {
+            final List<Method> interceptingMethods = Stream.of(context.getBundle().loadClass(clazz))
+                    .flatMap(c -> Stream.of(c.getMethods()))
+                    .filter(m -> m.isAnnotationPresent(AroundInvoke.class))
+                    .collect(toList());
+            switch (interceptingMethods.size()) {
+                case 0: // we can add @AroundConstruct later so let's already tolerate that
+                    return null;
+                case 1:
+                    return interceptingMethods.iterator().next();
+                default:
+                    throw new IllegalArgumentException("'" + clazz + "' must have a single @AroundInvoke method, found " + interceptingMethods);
+            }
+        } catch (final ClassNotFoundException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+}
+
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/InterceptorInvocationContext.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/InterceptorInvocationContext.java
new file mode 100644
index 0000000..ffa4acf
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/InterceptorInvocationContext.java
@@ -0,0 +1,106 @@
+/*
+ * 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.service.interceptor.impl.runtime.invoker;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.karaf.service.interceptor.api.InvocationContext;
+import org.apache.karaf.service.interceptor.impl.runtime.Exceptions;
+import org.apache.karaf.service.interceptor.impl.runtime.hook.InterceptorInstance;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class InterceptorInvocationContext<T> implements InvocationContext {
+    private final ServiceReference<T> interceptedReference;
+    private final Method method;
+    private final List<InterceptorInstance<?>> interceptors;
+
+    private T target;
+    private Map<String, Object> contextData;
+    private Object[] parameters;
+    private int index;
+
+    public InterceptorInvocationContext(final ServiceReference<T> reference,
+                                        final List<InterceptorInstance<?>> interceptors,
+                                        final Method method, final Object[] parameters) {
+        this.interceptedReference = reference;
+        this.method = method;
+        this.parameters = parameters;
+        this.interceptors = interceptors;
+    }
+
+    @Override
+    public Object proceed() throws Exception {
+        try {
+            if (index < interceptors.size()) {
+                final InterceptorInstance<?> interceptor = interceptors.get(index++);
+                try {
+                    return interceptor.intercept(this);
+                } catch (final Exception e) {
+                    index--;
+                    throw e;
+                }
+            }
+            try {
+                return getMethod().invoke(getTarget(), getParameters());
+            } catch (final InvocationTargetException ite) {
+                return Exceptions.unwrap(ite);
+            }
+        } finally {
+            if (target != null) { // todo: check scope and optimize it?
+                interceptedReference.getBundle().getBundleContext().ungetService(interceptedReference);
+            }
+        }
+    }
+
+    @Override
+    public T getTarget() {
+        final BundleContext context = interceptedReference.getBundle().getBundleContext();
+        target = context.getService(interceptedReference);
+        if (target == null) {
+            throw new IllegalStateException("service no more available (" + interceptedReference + ")");
+        }
+        return target;
+    }
+
+    @Override
+    public Method getMethod() {
+        return method;
+    }
+
+    @Override
+    public Object[] getParameters() {
+        return parameters;
+    }
+
+    @Override
+    public void setParameters(final Object[] parameters) {
+        this.parameters = parameters;
+    }
+
+    @Override
+    public Map<String, Object> getContextData() {
+        if (contextData == null) {
+            contextData = new HashMap<>();
+        }
+        return contextData;
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/package-info.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/package-info.java
new file mode 100644
index 0000000..96d06a1
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/invoker/package-info.java
@@ -0,0 +1,19 @@
+/*
+ * 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.
+ */
+
+// this is mainly inspired from OWB
+package org.apache.karaf.service.interceptor.impl.runtime.invoker;
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactory.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactory.java
new file mode 100644
index 0000000..ff07fd6
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactory.java
@@ -0,0 +1,663 @@
+/*
+ * 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.service.interceptor.impl.runtime.proxy;
+
+import static org.objectweb.asm.ClassReader.SKIP_CODE;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
+import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
+import static org.objectweb.asm.Opcodes.AALOAD;
+import static org.objectweb.asm.Opcodes.AASTORE;
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ACC_STATIC;
+import static org.objectweb.asm.Opcodes.ACC_SUPER;
+import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
+import static org.objectweb.asm.Opcodes.ACC_VARARGS;
+import static org.objectweb.asm.Opcodes.ACONST_NULL;
+import static org.objectweb.asm.Opcodes.ALOAD;
+import static org.objectweb.asm.Opcodes.ANEWARRAY;
+import static org.objectweb.asm.Opcodes.ARETURN;
+import static org.objectweb.asm.Opcodes.ASM7;
+import static org.objectweb.asm.Opcodes.ASTORE;
+import static org.objectweb.asm.Opcodes.ATHROW;
+import static org.objectweb.asm.Opcodes.BIPUSH;
+import static org.objectweb.asm.Opcodes.CHECKCAST;
+import static org.objectweb.asm.Opcodes.DLOAD;
+import static org.objectweb.asm.Opcodes.DRETURN;
+import static org.objectweb.asm.Opcodes.DUP;
+import static org.objectweb.asm.Opcodes.FLOAD;
+import static org.objectweb.asm.Opcodes.FRETURN;
+import static org.objectweb.asm.Opcodes.GETFIELD;
+import static org.objectweb.asm.Opcodes.GETSTATIC;
+import static org.objectweb.asm.Opcodes.ICONST_0;
+import static org.objectweb.asm.Opcodes.ICONST_1;
+import static org.objectweb.asm.Opcodes.ICONST_2;
+import static org.objectweb.asm.Opcodes.ICONST_3;
+import static org.objectweb.asm.Opcodes.ICONST_4;
+import static org.objectweb.asm.Opcodes.ICONST_5;
+import static org.objectweb.asm.Opcodes.IFEQ;
+import static org.objectweb.asm.Opcodes.ILOAD;
+import static org.objectweb.asm.Opcodes.INVOKEINTERFACE;
+import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
+import static org.objectweb.asm.Opcodes.INVOKESTATIC;
+import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
+import static org.objectweb.asm.Opcodes.IRETURN;
+import static org.objectweb.asm.Opcodes.LLOAD;
+import static org.objectweb.asm.Opcodes.LRETURN;
+import static org.objectweb.asm.Opcodes.NEW;
+import static org.objectweb.asm.Opcodes.POP;
+import static org.objectweb.asm.Opcodes.PUTFIELD;
+import static org.objectweb.asm.Opcodes.RETURN;
+import static org.objectweb.asm.Opcodes.SIPUSH;
+import static org.objectweb.asm.Opcodes.V10;
+import static org.objectweb.asm.Opcodes.V11;
+import static org.objectweb.asm.Opcodes.V12;
+import static org.objectweb.asm.Opcodes.V13;
+import static org.objectweb.asm.Opcodes.V1_8;
+import static org.objectweb.asm.Opcodes.V9;
+
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Stream;
+
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+
+// forked from OWB
+public class AsmProxyFactory {
+    private static final Method[] EMPTY_METHODS = new Method[0];
+
+    private static final String FIELD_INTERCEPTOR_HANDLER = "karafInterceptorProxyHandler";
+    private static final String FIELD_INTERCEPTED_METHODS = "karafInterceptorProxyMethods";
+
+    public <T> T create(final Class<?> clazz, final InterceptorHandler handler) {
+        try {
+            final T proxy = (T) clazz.getConstructor().newInstance();
+            final Field invocationHandlerField = clazz.getDeclaredField(FIELD_INTERCEPTOR_HANDLER);
+            invocationHandlerField.setAccessible(true);
+            invocationHandlerField.set(proxy, handler);
+            return proxy;
+        } catch (final IllegalAccessException | NoSuchFieldException | NoSuchMethodException | InstantiationException e) {
+            throw new IllegalStateException(e);
+        } catch (final InvocationTargetException ite) {
+            throw new IllegalStateException(ite.getTargetException());
+        }
+    }
+
+    public <T> Class<T> createProxyClass(final ProxyFactory.ProxyClassLoader classLoader,
+                                         final String proxyClassName, final Class<?>[] classesToProxy,
+                                         final Method[] interceptedMethods) {
+        try {
+            return (Class<T>) Class.forName(proxyClassName, true, classLoader);
+        } catch (final ClassNotFoundException cnfe) {
+            return doCreateProxyClass(classLoader, proxyClassName, classesToProxy, interceptedMethods);
+        }
+    }
+
+    private <T> Class<T> doCreateProxyClass(final ProxyFactory.ProxyClassLoader classLoader, final String proxyClassName,
+                                            final Class<?>[] classesToProxy, final Method[] interceptedMethods) {
+        final String proxyClassFileName = proxyClassName.replace('.', '/');
+        final byte[] proxyBytes = generateProxy(classesToProxy, proxyClassFileName, sortOutDuplicateMethods(interceptedMethods));
+        final Class<T> proxyCLass = classLoader.getOrRegister(proxyClassName, proxyBytes, classesToProxy[0].getPackage(), classesToProxy[0].getProtectionDomain());
+        try {
+            final Field interceptedMethodsField = proxyCLass.getDeclaredField(FIELD_INTERCEPTED_METHODS);
+            interceptedMethodsField.setAccessible(true);
+            interceptedMethodsField.set(null, interceptedMethods);
+        } catch (final Exception e) {
+            throw new IllegalStateException(e);
+        }
+        return proxyCLass;
+    }
+
+    private Method[] sortOutDuplicateMethods(final Method[] methods) {
+        if (methods == null || methods.length == 0) {
+            return null;
+        }
+
+        final List<Method> duplicates = new ArrayList<>();
+        for (final Method outer : methods) {
+            for (final Method inner : methods) {
+                if (inner != outer
+                        && hasSameSignature(outer, inner)
+                        && !(duplicates.contains(outer) || duplicates.contains(inner))) {
+                    duplicates.add(inner);
+                }
+            }
+        }
+
+        final List<Method> outsorted = new ArrayList<>(Arrays.asList(methods));
+        outsorted.removeAll(duplicates);
+        return outsorted.toArray(EMPTY_METHODS);
+    }
+
+    private boolean hasSameSignature(Method a, Method b) {
+        return a.getName().equals(b.getName())
+                && a.getReturnType().equals(b.getReturnType())
+                && Arrays.equals(a.getParameterTypes(), b.getParameterTypes());
+    }
+
+    private void createConstructor(final ClassWriter cw, final String proxyClassFileName, final Class<?> classToProxy,
+                                   final String classFileName) {
+        Constructor superDefaultCt;
+        String parentClassFileName = classFileName;
+        String descriptor = "()V";
+
+        try {
+            if (classToProxy.isInterface()) {
+                parentClassFileName = Type.getInternalName(Object.class);
+                superDefaultCt = Object.class.getConstructor(null);
+                descriptor = Type.getConstructorDescriptor(superDefaultCt);
+            }
+        } catch (final NoSuchMethodException nsme) {
+            // no worries
+        }
+
+        MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", descriptor, null, null);
+        mv.visitCode();
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitMethodInsn(INVOKESPECIAL, parentClassFileName, "<init>", descriptor, false);
+
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitInsn(ACONST_NULL);
+        mv.visitFieldInsn(PUTFIELD, proxyClassFileName, FIELD_INTERCEPTOR_HANDLER, Type.getDescriptor(InterceptorHandler.class));
+
+        mv.visitInsn(RETURN);
+        mv.visitMaxs(-1, -1);
+        mv.visitEnd();
+    }
+
+    private byte[] generateProxy(final Class<?>[] classesToProxy, final String proxyClassFileName, final Method[] interceptedMethods) {
+        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
+        final String classFileName = classesToProxy[0].getName().replace('.', '/');
+
+        final String[] interfaces = Stream.of(classesToProxy)
+                .filter(Class::isInterface)
+                .map(Type::getInternalName)
+                .toArray(String[]::new);
+        final String superClassName;
+        if (interfaces.length == classesToProxy.length) {
+            superClassName = Type.getInternalName(Object.class);
+        } else {
+            superClassName = Type.getInternalName(classesToProxy[0]);
+        }
+
+        cw.visit(findJavaVersion(classesToProxy[0]), ACC_PUBLIC + ACC_SUPER + ACC_SYNTHETIC, proxyClassFileName, null, superClassName, interfaces);
+        cw.visitSource(classFileName + ".java", null);
+        createInstanceVariables(cw);
+        createConstructor(cw, proxyClassFileName, classesToProxy[0], classFileName);
+        if (interceptedMethods != null) {
+            delegateInterceptedMethods(cw, proxyClassFileName, classesToProxy[0], interceptedMethods);
+        }
+        return cw.toByteArray();
+    }
+
+    private void createInstanceVariables(final ClassWriter cw) {
+        cw.visitField(ACC_PRIVATE,
+                FIELD_INTERCEPTOR_HANDLER, Type.getDescriptor(InterceptorHandler.class), null, null).visitEnd();
+        cw.visitField(ACC_PRIVATE | ACC_STATIC,
+                FIELD_INTERCEPTED_METHODS, Type.getDescriptor(Method[].class), null, null).visitEnd();
+    }
+
+    private void delegateInterceptedMethods(final ClassWriter cw,
+                                            final String proxyClassFileName, final Class<?> classToProxy,
+                                            final Method[] interceptedMethods) {
+        for (int i = 0; i < interceptedMethods.length; i++) {
+            if (!unproxyableMethod(interceptedMethods[i])) {
+                generateInterceptorHandledMethod(cw, interceptedMethods[i], i, classToProxy, proxyClassFileName);
+            }
+        }
+    }
+
+    private void generateInterceptorHandledMethod(final ClassWriter cw, final Method method, final int methodIndex,
+                                                  final Class<?> classToProxy, final String proxyClassFileName) {
+        if ("<init>".equals(method.getName())) {
+            return;
+        }
+
+        final Class<?> returnType = method.getReturnType();
+        final Class<?>[] parameterTypes = method.getParameterTypes();
+        final Class<?>[] exceptionTypes = method.getExceptionTypes();
+        final int modifiers = method.getModifiers();
+        if (Modifier.isFinal(modifiers) || Modifier.isStatic(modifiers)) {
+            throw new IllegalStateException("It's not possible to proxy a final or static method: " + classToProxy.getName() + " " + method.getName());
+        }
+
+        // push the method definition
+        final int modifier = modifiers & (ACC_PUBLIC | ACC_PROTECTED | ACC_VARARGS);
+
+        final MethodVisitor mv = cw.visitMethod(modifier, method.getName(), Type.getMethodDescriptor(method), null, null);
+        mv.visitCode();
+        // push try/catch block, to catch declared exceptions, and to catch java.lang.Throwable
+        final Label l0 = new Label();
+        final Label l1 = new Label();
+        final Label l2 = new Label();
+
+        if (exceptionTypes.length > 0) {
+            mv.visitTryCatchBlock(l0, l1, l2, "java/lang/reflect/InvocationTargetException");
+        }
+
+        // push try code
+        mv.visitLabel(l0);
+        final String classNameToOverride = method.getDeclaringClass().getName().replace('.', '/');
+        mv.visitLdcInsn(Type.getType("L" + classNameToOverride + ";"));
+
+        // the following code generates the bytecode for this line of Java:
+        // Method method = <proxy>.class.getMethod("add", new Class[] { <array of function argument classes> });
+
+        // get the method name to invoke, and push to stack
+        mv.visitLdcInsn(method.getName());
+
+        // create the Class[]
+        createArrayDefinition(mv, parameterTypes.length, Class.class);
+
+        int length = 1;
+
+        // push parameters into array
+        for (int i = 0; i < parameterTypes.length; i++) {
+            // keep copy of array on stack
+            mv.visitInsn(DUP);
+
+            final Class<?> parameterType = parameterTypes[i];
+
+            // push number onto stack
+            pushIntOntoStack(mv, i);
+
+            if (parameterType.isPrimitive()) {
+                String wrapperType = getWrapperType(parameterType);
+                mv.visitFieldInsn(GETSTATIC, wrapperType, "TYPE", "Ljava/lang/Class;");
+            } else {
+                mv.visitLdcInsn(Type.getType(parameterType));
+            }
+
+            mv.visitInsn(AASTORE);
+
+            if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType)) {
+                length += 2;
+            } else {
+                length++;
+            }
+        }
+
+        // the following code generates bytecode equivalent to:
+        // return ((<returntype>) invocationHandler.invoke(this, {methodIndex}, new Object[] { <function arguments }))[.<primitive>Value()];
+
+        final Label l4 = new Label();
+        mv.visitLabel(l4);
+        mv.visitVarInsn(ALOAD, 0);
+        mv.visitFieldInsn(GETFIELD, proxyClassFileName, FIELD_INTERCEPTOR_HANDLER, Type.getDescriptor(InterceptorHandler.class));
+        mv.visitFieldInsn(GETSTATIC, proxyClassFileName, FIELD_INTERCEPTED_METHODS, Type.getDescriptor(Method[].class));
+        if (methodIndex < 128) {
+            mv.visitIntInsn(BIPUSH, methodIndex);
+        } else if (methodIndex < 32267) {
+            mv.visitIntInsn(SIPUSH, methodIndex);
+        } else {
+            throw new IllegalStateException("Sorry, we only support Classes with 2^15 methods...");
+        }
+
+        mv.visitInsn(AALOAD);
+        pushMethodParameterArray(mv, parameterTypes);
+        mv.visitMethodInsn(INVOKEINTERFACE, Type.getInternalName(InterceptorHandler.class), "invoke",
+                "(Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;", true);
+        mv.visitTypeInsn(CHECKCAST, getCastType(returnType));
+        if (returnType.isPrimitive() && (!Void.TYPE.equals(returnType))) {
+            // get the primitive value
+            mv.visitMethodInsn(INVOKEVIRTUAL, getWrapperType(returnType), getPrimitiveMethod(returnType),
+                    "()" + Type.getDescriptor(returnType), false);
+        }
+
+        mv.visitLabel(l1);
+        if (!Void.TYPE.equals(returnType)) {
+            mv.visitInsn(getReturnInsn(returnType));
+        } else {
+            mv.visitInsn(POP);
+            mv.visitInsn(RETURN);
+        }
+
+        // catch InvocationTargetException
+        if (exceptionTypes.length > 0) {
+            mv.visitLabel(l2);
+            mv.visitVarInsn(ASTORE, length);
+
+            Label l5 = new Label();
+            mv.visitLabel(l5);
+
+            for (int i = 0; i < exceptionTypes.length; i++) {
+                Class<?> exceptionType = exceptionTypes[i];
+
+                mv.visitLdcInsn(Type.getType("L" + exceptionType.getCanonicalName().replace('.', '/') + ";"));
+                mv.visitVarInsn(ALOAD, length);
+                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
+                        "()Ljava/lang/Throwable;", false);
+                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;", false);
+                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z", false);
+
+                Label l6 = new Label();
+                mv.visitJumpInsn(IFEQ, l6);
+
+                Label l7 = new Label();
+                mv.visitLabel(l7);
+
+                mv.visitVarInsn(ALOAD, length);
+                mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/reflect/InvocationTargetException", "getCause",
+                        "()Ljava/lang/Throwable;", false);
+                mv.visitTypeInsn(CHECKCAST, getCastType(exceptionType));
+                mv.visitInsn(ATHROW);
+                mv.visitLabel(l6);
+
+                if (i == (exceptionTypes.length - 1)) {
+                    mv.visitTypeInsn(NEW, "java/lang/reflect/UndeclaredThrowableException");
+                    mv.visitInsn(DUP);
+                    mv.visitVarInsn(ALOAD, length);
+                    mv.visitMethodInsn(INVOKESPECIAL, "java/lang/reflect/UndeclaredThrowableException", "<init>",
+                            "(Ljava/lang/Throwable;)V", false);
+                    mv.visitInsn(ATHROW);
+                }
+            }
+        }
+
+        mv.visitMaxs(0, 0);
+        mv.visitEnd();
+    }
+
+    private int findJavaVersion(final Class<?> from) {
+        final String resource = from.getName().replace('.', '/') + ".class";
+        try (final InputStream stream = from.getClassLoader().getResourceAsStream(resource)) {
+            if (stream == null) {
+                return V1_8;
+            }
+            final ClassReader reader = new ClassReader(stream);
+            final VersionVisitor visitor = new VersionVisitor();
+            reader.accept(visitor, SKIP_DEBUG + SKIP_CODE + SKIP_FRAMES);
+            if (visitor.version != 0) {
+                return visitor.version;
+            }
+        } catch (final Exception e) {
+            // no-op
+        }
+        // mainly for JVM classes - outside the classloader, find to fallback on the JVM version
+        final String javaVersionProp = System.getProperty("java.version", "1.8");
+        if (javaVersionProp.startsWith("1.8")) {
+            return V1_8;
+        } else if (javaVersionProp.startsWith("9") || javaVersionProp.startsWith("1.9")) {
+            return V9;
+        } else if (javaVersionProp.startsWith("10")) {
+            return V10;
+        } else if (javaVersionProp.startsWith("11")) {
+            return V11;
+        } else if (javaVersionProp.startsWith("12")) {
+            return V12;
+        } else if (javaVersionProp.startsWith("13")) {
+            return V13;
+        }
+        try {
+            final int i = Integer.parseInt(javaVersionProp);
+            if (i > 13) {
+                return V13 + (i - 13);
+            }
+            return V1_8;
+        } catch (final NumberFormatException nfe) {
+            return V1_8;
+        }
+    }
+
+    private boolean unproxyableMethod(final Method delegatedMethod) {
+        final int modifiers = delegatedMethod.getModifiers();
+        return (modifiers & (Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL | Modifier.NATIVE)) > 0 ||
+                "finalize".equals(delegatedMethod.getName()) || delegatedMethod.isBridge();
+    }
+
+    /**
+     * @return the wrapper type for a primitive, e.g. java.lang.Integer for int
+     */
+    private String getWrapperType(Class<?> type) {
+        if (Integer.TYPE.equals(type)) {
+            return Integer.class.getCanonicalName().replace('.', '/');
+        } else if (Boolean.TYPE.equals(type)) {
+            return Boolean.class.getCanonicalName().replace('.', '/');
+        } else if (Character.TYPE.equals(type)) {
+            return Character.class.getCanonicalName().replace('.', '/');
+        } else if (Byte.TYPE.equals(type)) {
+            return Byte.class.getCanonicalName().replace('.', '/');
+        } else if (Short.TYPE.equals(type)) {
+            return Short.class.getCanonicalName().replace('.', '/');
+        } else if (Float.TYPE.equals(type)) {
+            return Float.class.getCanonicalName().replace('.', '/');
+        } else if (Long.TYPE.equals(type)) {
+            return Long.class.getCanonicalName().replace('.', '/');
+        } else if (Double.TYPE.equals(type)) {
+            return Double.class.getCanonicalName().replace('.', '/');
+        } else if (Void.TYPE.equals(type)) {
+            return Void.class.getCanonicalName().replace('.', '/');
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    /**
+     * Returns the appropriate bytecode instruction to load a value from a variable to the stack
+     *
+     * @param type Type to load
+     * @return Bytecode instruction to use
+     */
+    private int getVarInsn(Class<?> type) {
+        if (type.isPrimitive()) {
+            if (Integer.TYPE.equals(type)) {
+                return ILOAD;
+            } else if (Boolean.TYPE.equals(type)) {
+                return ILOAD;
+            } else if (Character.TYPE.equals(type)) {
+                return ILOAD;
+            } else if (Byte.TYPE.equals(type)) {
+                return ILOAD;
+            } else if (Short.TYPE.equals(type)) {
+                return ILOAD;
+            } else if (Float.TYPE.equals(type)) {
+                return FLOAD;
+            } else if (Long.TYPE.equals(type)) {
+                return LLOAD;
+            } else if (Double.TYPE.equals(type)) {
+                return DLOAD;
+            }
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    /**
+     * Invokes the most appropriate bytecode instruction to put a number on the stack
+     *
+     * @param mv
+     * @param i
+     */
+    private void pushIntOntoStack(final MethodVisitor mv, final int i) {
+        if (i == 0) {
+            mv.visitInsn(ICONST_0);
+        } else if (i == 1) {
+            mv.visitInsn(ICONST_1);
+        } else if (i == 2) {
+            mv.visitInsn(ICONST_2);
+        } else if (i == 3) {
+            mv.visitInsn(ICONST_3);
+        } else if (i == 4) {
+            mv.visitInsn(ICONST_4);
+        } else if (i == 5) {
+            mv.visitInsn(ICONST_5);
+        } else if (i > 5 && i <= 255) {
+            mv.visitIntInsn(BIPUSH, i);
+        } else {
+            mv.visitIntInsn(SIPUSH, i);
+        }
+    }
+
+    /**
+     * Gets the appropriate bytecode instruction for RETURN, according to what type we need to return
+     *
+     * @param type Type the needs to be returned
+     * @return The matching bytecode instruction
+     */
+    private int getReturnInsn(final Class<?> type) {
+        if (type.isPrimitive()) {
+            if (Void.TYPE.equals(type)) {
+                return RETURN;
+            }
+            if (Integer.TYPE.equals(type)) {
+                return IRETURN;
+            } else if (Boolean.TYPE.equals(type)) {
+                return IRETURN;
+            } else if (Character.TYPE.equals(type)) {
+                return IRETURN;
+            } else if (Byte.TYPE.equals(type)) {
+                return IRETURN;
+            } else if (Short.TYPE.equals(type)) {
+                return IRETURN;
+            } else if (Float.TYPE.equals(type)) {
+                return FRETURN;
+            } else if (Long.TYPE.equals(type)) {
+                return LRETURN;
+            } else if (Double.TYPE.equals(type)) {
+                return DRETURN;
+            }
+        }
+        return ARETURN;
+    }
+
+    /**
+     * Gets the string to use for CHECKCAST instruction, returning the correct value for any type, including primitives and arrays
+     *
+     * @param returnType The type to cast to with CHECKCAST
+     * @return CHECKCAST parameter
+     */
+    private String getCastType(Class<?> returnType) {
+        if (returnType.isPrimitive()) {
+            return getWrapperType(returnType);
+        } else {
+            return Type.getInternalName(returnType);
+        }
+    }
+
+    /**
+     * Returns the name of the Java method to call to get the primitive value from an Object - e.g. intValue for java.lang.Integer
+     *
+     * @param type Type whose primitive method we want to lookup
+     * @return The name of the method to use
+     */
+    private String getPrimitiveMethod(final Class<?> type) {
+        if (Integer.TYPE.equals(type)) {
+            return "intValue";
+        } else if (Boolean.TYPE.equals(type)) {
+            return "booleanValue";
+        } else if (Character.TYPE.equals(type)) {
+            return "charValue";
+        } else if (Byte.TYPE.equals(type)) {
+            return "byteValue";
+        } else if (Short.TYPE.equals(type)) {
+            return "shortValue";
+        } else if (Float.TYPE.equals(type)) {
+            return "floatValue";
+        } else if (Long.TYPE.equals(type)) {
+            return "longValue";
+        } else if (Double.TYPE.equals(type)) {
+            return "doubleValue";
+        }
+
+        throw new IllegalStateException("Type: " + type.getCanonicalName() + " is not a primitive type");
+    }
+
+    private void generateReturn(final MethodVisitor mv, final Method delegatedMethod) {
+        final Class<?> returnType = delegatedMethod.getReturnType();
+        mv.visitInsn(getReturnInsn(returnType));
+    }
+
+    /**
+     * Create an Object[] parameter which contains all the parameters of the currently invoked method
+     * and store this array for use in the call stack.
+     *
+     * @param mv
+     * @param parameterTypes
+     */
+    private void pushMethodParameterArray(MethodVisitor mv, Class<?>[] parameterTypes) {
+        // need to construct the array of objects passed in
+        // create the Object[]
+        createArrayDefinition(mv, parameterTypes.length, Object.class);
+
+        int index = 1;
+        for (int i = 0; i < parameterTypes.length; i++) {
+            // keep copy of array on stack
+            mv.visitInsn(DUP);
+
+            final Class<?> parameterType = parameterTypes[i];
+            pushIntOntoStack(mv, i);
+
+            if (parameterType.isPrimitive()) {
+                final String wrapperType = getWrapperType(parameterType);
+                mv.visitVarInsn(getVarInsn(parameterType), index);
+                mv.visitMethodInsn(INVOKESTATIC, wrapperType, "valueOf",
+                        "(" + Type.getDescriptor(parameterType) + ")L" + wrapperType + ";", false);
+                mv.visitInsn(AASTORE);
+
+                if (Long.TYPE.equals(parameterType) || Double.TYPE.equals(parameterType)) {
+                    index += 2;
+                } else {
+                    index++;
+                }
+            } else {
+                mv.visitVarInsn(ALOAD, index);
+                mv.visitInsn(AASTORE);
+                index++;
+            }
+        }
+    }
+
+    private void createArrayDefinition(final MethodVisitor mv, final int size, final Class<?> type) {
+        if (size < 0) {
+            throw new IllegalStateException("Array size cannot be less than zero");
+        }
+        pushIntOntoStack(mv, size);
+        mv.visitTypeInsn(ANEWARRAY, type.getCanonicalName().replace('.', '/'));
+    }
+
+
+    private static class VersionVisitor extends ClassVisitor {
+        private int version;
+
+        private VersionVisitor() {
+            super(ASM7);
+        }
+
+        @Override
+        public void visit(final int version, final int access, final String name,
+                          final String signature, final String superName, final String[] interfaces) {
+            this.version = version;
+        }
+    }
+
+    public interface InterceptorHandler {
+        Object invoke(Method method, Object[] args) throws Exception;
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/ProxyFactory.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/ProxyFactory.java
new file mode 100644
index 0000000..0fe2479
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/ProxyFactory.java
@@ -0,0 +1,222 @@
+/*
+ * 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.service.interceptor.impl.runtime.proxy;
+
+import static java.util.Collections.emptyList;
+import static java.util.Optional.ofNullable;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Proxy;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.impl.runtime.hook.InterceptorInstance;
+import org.apache.karaf.service.interceptor.impl.runtime.invoker.InterceptorInvocationContext;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.ServiceReference;
+
+public class ProxyFactory {
+
+    private static final Class<?>[] EMPTY_CLASSES = new Class<?>[0];
+
+    public <T> T create(final ServiceReference<T> ref, final List<Class<?>> classes,
+                        final List<InterceptorInstance<?>> interceptors,
+                        final Map<Method, List<Class<?>>> interceptorsPerMethod) {
+        if (classes.isEmpty()) {
+            throw new IllegalArgumentException("Can't proxy an empty list of type: " + ref);
+        }
+
+        final Map<Method, List<InterceptorInstance<?>>> interceptorInstancePerMethod = interceptorsPerMethod.entrySet().stream()
+            .collect(toMap(Map.Entry::getKey, m -> m.getValue().stream()
+                    .map(binding -> interceptors.stream().filter(i -> i.getBinding() == binding).findFirst().orElse(null))
+                    .collect(toList())));
+
+        final ProxyClassLoader loader = new ProxyClassLoader(Thread.currentThread().getContextClassLoader(), ref.getBundle());
+        if (classes.stream().allMatch(Class::isInterface)) {
+            final Object proxyInstance = Proxy.newProxyInstance(
+                    loader,
+                    classes.toArray(EMPTY_CLASSES),
+                    (proxy, method, args) -> doInvoke(ref, method, args, interceptorInstancePerMethod));
+            return (T) proxyInstance;
+        }
+        final AsmProxyFactory asm = new AsmProxyFactory();
+        final Class<?> proxyClass = asm.createProxyClass(
+                loader,
+                getProxyClassName(classes),
+                classes.stream().sorted(this::compareClasses).toArray(Class<?>[]::new),
+                findInterceptedMethods(classes));
+        return asm.create(proxyClass, (method, args) -> doInvoke(ref, method, args, interceptorInstancePerMethod));
+    }
+
+    private <T> Object doInvoke(final ServiceReference<T> ref,
+                                final Method method, final Object[] args,
+                                final Map<Method, List<InterceptorInstance<?>>> interceptorsPerMethod) throws Exception {
+        final List<InterceptorInstance<?>> methodInterceptors = interceptorsPerMethod.getOrDefault(method, emptyList());
+        return new InterceptorInvocationContext<>(ref, methodInterceptors, method, args).proceed();
+    }
+
+    private int compareClasses(final Class<?> c1, final Class<?> c2) {
+        if (c1 == c2) {
+            return 0;
+        }
+        if (c1.isAssignableFrom(c2)) {
+            return 1;
+        }
+        if (c2.isAssignableFrom(c1)) {
+            return -1;
+        }
+        if (c1.isInterface() && !c2.isInterface()) {
+            return 1;
+        }
+        if (c2.isInterface() && !c1.isInterface()) {
+            return -1;
+        }
+        if (!c1.isInterface() && !c2.isInterface()) {
+            throw new IllegalArgumentException("No common class between " + c1 + " and " + c2);
+        }
+        return c1.getName().compareTo(c2.getName()); // just to be deterministic
+    }
+
+    private Method[] findInterceptedMethods(final List<Class<?>> classes) {
+        return classes.stream()
+                .flatMap(c -> c.isInterface() ? Stream.of(c.getMethods()) : findMethods(c))
+                .distinct()
+                .filter(method -> Modifier.isPublic(method.getModifiers())) // todo: enable protected? not that scr friendly but doable
+                .toArray(Method[]::new);
+    }
+
+    private Stream<Method> findMethods(final Class<?> clazz) {
+        return clazz == null || Object.class == clazz ?
+                Stream.empty() :
+                Stream.concat(Stream.of(clazz.getDeclaredMethods()), findMethods(clazz.getSuperclass()));
+    }
+
+    private String getProxyClassName(final List<Class<?>> classes) {
+        return classes.iterator().next().getName() + "$$KarafInterceptorProxy" +
+                classes.stream().skip(1).map(c -> c.getName().replace(".", "_").replace("$", "")).collect(joining("__"));
+    }
+
+    static class ProxyClassLoader extends ClassLoader {
+        private final Bundle bundle;
+        private final Map<String, Class<?>> classes = new ConcurrentHashMap<>();
+
+        ProxyClassLoader(final ClassLoader parent, final Bundle bundle) {
+            super(parent);
+            this.bundle = bundle;
+        }
+
+        @Override
+        protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
+            final Class<?> clazz = classes.get(name);
+            if (clazz != null) {
+                return clazz;
+            }
+            if (bundle != null) {
+                try {
+                    return bundle.loadClass(name);
+                } catch (final ClassNotFoundException cnfe) {
+                    if (name != null && name.startsWith("org.apache.karaf.service.interceptor.")) {
+                        return getClass().getClassLoader().loadClass(name);
+                    }
+                    throw cnfe;
+                }
+            }
+            return super.loadClass(name, resolve);
+        }
+
+        @Override
+        public URL getResource(final String name) {
+            return bundle.getResource(name);
+        }
+
+        @Override
+        public Enumeration<URL> getResources(final String name) throws IOException {
+            return bundle.getResources(name);
+        }
+
+        @Override
+        public InputStream getResourceAsStream(final String name) {
+            return ofNullable(getResource(name)).map(u -> {
+                try {
+                    return u.openStream();
+                } catch (final IOException e) {
+                    throw new IllegalStateException(e);
+                }
+            }).orElse(null);
+        }
+
+        public <T> Class<T> getOrRegister(final String proxyClassName, final byte[] proxyBytes,
+                                          final Package pck, final ProtectionDomain protectionDomain) {
+            final String key = proxyClassName.replace('/', '.');
+            Class<?> existing = classes.get(key);
+            if (existing == null) {
+                synchronized (this) {
+                    existing = classes.get(key);
+                    if (existing == null) {
+                        definePackageFor(pck, protectionDomain);
+                        existing = super.defineClass(proxyClassName, proxyBytes, 0, proxyBytes.length);
+                        resolveClass(existing);
+                        classes.put(key, existing);
+                    }
+                }
+            }
+            return (Class<T>) existing;
+        }
+
+        private void definePackageFor(final Package model, final ProtectionDomain protectionDomain) {
+            if (model == null) {
+                return;
+            }
+            if (getPackage(model.getName()) == null) {
+                if (model.isSealed() && protectionDomain != null &&
+                        protectionDomain.getCodeSource() != null &&
+                        protectionDomain.getCodeSource().getLocation() != null) {
+                    definePackage(
+                            model.getName(),
+                            model.getSpecificationTitle(),
+                            model.getSpecificationVersion(),
+                            model.getSpecificationVendor(),
+                            model.getImplementationTitle(),
+                            model.getImplementationVersion(),
+                            model.getImplementationVendor(),
+                            protectionDomain.getCodeSource().getLocation());
+                } else {
+                    definePackage(
+                            model.getName(),
+                            model.getSpecificationTitle(),
+                            model.getSpecificationVersion(),
+                            model.getSpecificationVendor(),
+                            model.getImplementationTitle(),
+                            model.getImplementationVersion(),
+                            model.getImplementationVendor(),
+                            null);
+                }
+            }
+        }
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptedServiceRegistry.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptedServiceRegistry.java
new file mode 100644
index 0000000..f9da914
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptedServiceRegistry.java
@@ -0,0 +1,142 @@
+/*
+ * 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.service.interceptor.impl.runtime.registry;
+
+import static java.util.Optional.ofNullable;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.api.InterceptorBinding;
+import org.apache.karaf.service.interceptor.impl.runtime.PropertiesManager;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+import org.osgi.framework.ServiceReference;
+
+public class InterceptedServiceRegistry implements ServiceListener {
+    private final PropertiesManager propertiesManager;
+    private final Consumer<ServiceReference<?>> onServiceAddition;
+    private final Consumer<ServiceReference<?>> onServiceRemoval;
+    private final Map<ServiceReference<?>, RegistrationState> references = new ConcurrentHashMap<>();
+
+    public InterceptedServiceRegistry(final Consumer<ServiceReference<?>> onServiceAddition,
+                                      final Consumer<ServiceReference<?>> onServiceRemoval,
+                                      final PropertiesManager propertiesManager) {
+        this.onServiceAddition = onServiceAddition;
+        this.onServiceRemoval = onServiceRemoval;
+        this.propertiesManager = propertiesManager;
+    }
+
+    @Override
+    public void serviceChanged(final ServiceEvent serviceEvent) {
+        final ServiceReference<?> ref = serviceEvent.getServiceReference();
+        switch (serviceEvent.getType()) {
+            case ServiceEvent.REGISTERED:
+                doRegister(ref);
+                break;
+            case ServiceEvent.MODIFIED_ENDMATCH:
+            case ServiceEvent.UNREGISTERING:
+                doRemove(ref);
+                break;
+            case ServiceEvent.MODIFIED:
+                ofNullable(references.get(ref))
+                        .filter(reg -> didChange(ref, reg))
+                        .ifPresent(reg -> {
+                            doRemove(ref);
+                            doRegister(ref);
+                        });
+            default:
+        }
+    }
+
+    private boolean didChange(final ServiceReference<?> ref, final RegistrationState reg) {
+        return !reg.registrationProperties.equals(propertiesManager.collectProperties(ref)) ||
+                !reg.bindingsPerMethod.equals(computeBindings(ref));
+    }
+
+    private void doRegister(final ServiceReference<?> ref) {
+        references.put(ref, new RegistrationState(propertiesManager.collectProperties(ref), computeBindings(ref)));
+        onServiceAddition.accept(ref);
+    }
+
+    private void doRemove(final ServiceReference<?> ref) {
+        onServiceRemoval.accept(ref);
+        references.remove(ref);
+    }
+
+    private Map<Method, List<Class<?>>> computeBindings(final ServiceReference<?> ref) {
+        final List<Class<?>> types = propertiesManager.unflattenStringValues(ref.getProperty(Constants.OBJECTCLASS))
+                .map(it -> {
+                    try {
+                        return ref.getBundle().loadClass(it);
+                    } catch (final ClassNotFoundException e) {
+                        throw new IllegalStateException(e);
+                    }
+                })
+                .distinct()
+                .collect(toList());
+        final Collection<Annotation> globalInterceptors = types.stream()
+                .flatMap(type -> Stream.of(type.getAnnotations()))
+                .filter(methodAnnotation -> methodAnnotation.annotationType().isAnnotationPresent(InterceptorBinding.class))
+                .distinct()
+                .collect(toList());
+        return types.stream()
+                .flatMap(type -> Stream.of(type.getMethods()))
+                .collect(toMap(identity(), m -> Stream.concat(
+                        globalInterceptors.stream(),
+                        Stream.of(m.getAnnotations()))
+                        .filter(methodAnnotation -> methodAnnotation.annotationType().isAnnotationPresent(InterceptorBinding.class))
+                        .distinct()
+                        .map(Annotation::annotationType) // todo: keep Annotation with values
+                        .collect(toList())));
+    }
+
+    public <T> Stream<Class<?>> getBindings(final ServiceReference<T> ref) {
+        return ofNullable(references.get(ref))
+                .map(reg -> reg.bindingsPerMethod.values().stream().flatMap(Collection::stream).distinct())
+                .orElseGet(Stream::empty);
+    }
+
+    public <T> Map<Method, List<Class<?>>> getInterceptorsPerMethod(final ServiceReference<T> ref) {
+        return ofNullable(references.get(ref))
+                .map(reg -> reg.bindingsPerMethod)
+                .orElseGet(Collections::emptyMap);
+    }
+
+    private static class RegistrationState {
+        private final Hashtable<String, Object> registrationProperties;
+        private final Map<Method, List<Class<?>>> bindingsPerMethod;
+
+        private RegistrationState(final Hashtable<String, Object> registrationProperties,
+                                  final Map<Method, List<Class<?>>> bindingsPerMethod) {
+            this.registrationProperties = registrationProperties;
+            this.bindingsPerMethod = bindingsPerMethod;
+        }
+    }
+}
diff --git a/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptorRegistry.java b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptorRegistry.java
new file mode 100644
index 0000000..accca67
--- /dev/null
+++ b/services/interceptor/impl/src/main/java/org/apache/karaf/service/interceptor/impl/runtime/registry/InterceptorRegistry.java
@@ -0,0 +1,97 @@
+/*
+ * 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.service.interceptor.impl.runtime.registry;
+
+import static java.util.stream.Collectors.toList;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.karaf.service.interceptor.api.InterceptorBinding;
+import org.apache.karaf.service.interceptor.impl.runtime.PropertiesManager;
+import org.apache.karaf.service.interceptor.impl.runtime.hook.InterceptorInstance;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
+
+public class InterceptorRegistry implements ServiceListener {
+    private final Consumer<Class<?>> onAddition;
+    private final Consumer<Class<?>> onRemoval;
+    private final PropertiesManager propertiesManager;
+    private final Map<Class<?>, InterceptorInstance<?>> interceptors = new ConcurrentHashMap<>();
+
+    public InterceptorRegistry(final Consumer<Class<?>> onAddition,
+                               final Consumer<Class<?>> onRemoval,
+                               final PropertiesManager propertiesManager) {
+        this.onAddition = onAddition;
+        this.onRemoval = onRemoval;
+        this.propertiesManager = propertiesManager;
+    }
+
+    public boolean areBindingsAvailable(final Stream<Class<?>> bindings) {
+        return bindings.allMatch(binding -> binding != null && interceptors.containsKey(binding));
+    }
+
+    public List<InterceptorInstance<?>> getInterceptors(final List<Class<?>> bindings) {
+        return bindings.stream().map(interceptors::get).distinct().collect(toList());
+    }
+
+    @Override
+    public void serviceChanged(final ServiceEvent serviceEvent) {
+        final Class<? extends Annotation> bindingClass = getInterceptorBinding(serviceEvent);
+        switch (serviceEvent.getType()) {
+            case ServiceEvent.REGISTERED: {
+                interceptors.put(bindingClass, new InterceptorInstance<>(
+                        serviceEvent.getServiceReference(), bindingClass, propertiesManager));
+                onAddition.accept(bindingClass);
+                break;
+            }
+            case ServiceEvent.MODIFIED_ENDMATCH:
+            case ServiceEvent.UNREGISTERING: {
+                interceptors.remove(bindingClass);
+                onRemoval.accept(bindingClass);
+                break;
+            }
+            case ServiceEvent.MODIFIED:
+            default:
+        }
+    }
+
+    private Class<? extends Annotation> getInterceptorBinding(final ServiceEvent serviceEvent) {
+        final List<Annotation> bindings = propertiesManager.unflattenStringValues(serviceEvent.getServiceReference().getProperty(Constants.OBJECTCLASS))
+                .map(it -> {
+                    try {
+                        return serviceEvent.getServiceReference().getBundle().loadClass(it);
+                    } catch (final ClassNotFoundException e) {
+                        throw new IllegalStateException(e);
+                    }
+                })
+                .flatMap(it -> Stream.of(it.getAnnotations()))
+                .filter(it -> it.annotationType().isAnnotationPresent(InterceptorBinding.class))
+                .distinct()
+                .collect(toList());
+        if (bindings.size() != 1) {
+            throw new IllegalArgumentException("A single @InterceptorBinding on " + serviceEvent + " is required, found: " + bindings);
+        }
+        // todo: keep annotation instance to support binding values?
+        return bindings.iterator().next().annotationType();
+    }
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/E2ETest.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/E2ETest.java
new file mode 100644
index 0000000..d4e35fa
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/E2ETest.java
@@ -0,0 +1,120 @@
+/*
+ * 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.service.interceptor.impl;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.ops4j.pax.exam.CoreOptions.bundle;
+import static org.ops4j.pax.exam.CoreOptions.systemTimeout;
+import static org.ops4j.pax.exam.CoreOptions.url;
+import static org.ops4j.pax.exam.container.remote.RBCRemoteTargetOptions.waitForRBCFor;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureConsole;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.configureSecurity;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.editConfigurationFilePut;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.features;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.karafDistributionConfiguration;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.keepRuntimeFolder;
+import static org.ops4j.pax.exam.karaf.options.KarafDistributionOption.logLevel;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.util.Optional;
+
+import javax.inject.Inject;
+
+import org.apache.karaf.service.interceptor.impl.test.InterceptedService;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.ProbeBuilder;
+import org.ops4j.pax.exam.TestProbeBuilder;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.LogLevelOption;
+import org.ops4j.pax.exam.options.UrlProvisionOption;
+import org.ops4j.pax.exam.options.UrlReference;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+import org.osgi.framework.Constants;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class E2ETest {
+    @Inject
+    private InterceptedService interceptedService;
+
+    @Test
+    @Ignore
+    public void run() {
+        assertTrue(interceptedService.getClass().getName().contains("$$KarafInterceptorProxy"));
+        assertEquals("wrapped>from 'org.apache.karaf.service.interceptor.impl.test.InterceptedService'<", interceptedService.wrap());
+        assertEquals("wrapped>'bar'(suffixed)<", interceptedService.wrapAndSuffix("bar"));
+    }
+
+    @ProbeBuilder
+    public TestProbeBuilder probeConfiguration(final TestProbeBuilder probe) {
+        probe.setHeader(Constants.EXPORT_PACKAGE, "org.apache.karaf.service.interceptor.impl.test");
+        probe.setHeader("Service-Component",
+                "OSGI-INF/org.apache.karaf.service.interceptor.impl.test.InterceptedService.xml," +
+                "OSGI-INF/org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor.xml," +
+                "OSGI-INF/org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor.xml");
+        return probe;
+    }
+
+    @Configuration
+    public Option[] config() throws MalformedURLException {
+        final String localRepository = System.getProperty("org.ops4j.pax.url.mvn.localRepository", "");
+        final UrlReference karafUrl = url(new File("target/libs/karaf.tar.gz").toURI().toURL().toExternalForm());
+        final UrlReference asmUrl = url(new File("target/libs/asm.jar").toURI().toURL().toExternalForm());
+        final UrlProvisionOption apiBundle = url(Optional.ofNullable(new File("../api/target")
+                .listFiles((dir, name) -> name.startsWith("org.apache.karaf.services.interceptor.api-") && isNotReleaseArtifact(name)))
+            .map(files -> files[0])
+            .orElseThrow(() -> new IllegalArgumentException("No interceptor api bundle found, ensure api module was built"))
+            .toURI().toURL().toExternalForm());
+        final UrlProvisionOption implBundle = url(Optional.ofNullable(new File("target")
+                .listFiles((dir, name) -> name.startsWith("org.apache.karaf.services.interceptor.impl-") && isNotReleaseArtifact(name)))
+            .map(files -> files[0])
+            .orElseThrow(() -> new IllegalArgumentException("No interceptor impl bundle found, ensure impl module was built"))
+            .toURI().toURL().toExternalForm());
+        return new Option[]{
+                karafDistributionConfiguration()
+                        .frameworkUrl(karafUrl.getURL())
+                        .name("Apache Karaf")
+                        .runEmbedded(true)
+                        .unpackDirectory(new File("target/exam")),
+                configureSecurity().disableKarafMBeanServerBuilder(),
+                configureConsole().ignoreLocalConsole(),
+                keepRuntimeFolder(),
+                logLevel(LogLevelOption.LogLevel.INFO),
+                systemTimeout(3600000),
+                waitForRBCFor(3600000),
+                editConfigurationFilePut("etc/org.apache.karaf.features.cfg", "updateSnapshots", "none"),
+                editConfigurationFilePut("etc/org.ops4j.pax.url.mvn.cfg", "org.ops4j.pax.url.mvn.localRepository", localRepository),
+                editConfigurationFilePut("etc/branding.properties", "welcome", ""), // No welcome banner
+                editConfigurationFilePut("etc/branding-ssh.properties", "welcome", ""),
+                features("mvn:org.apache.karaf.features/standard/" + System.getProperty("karaf.version") + "/xml/features", "scr"),
+                bundle(asmUrl.getURL()),
+                bundle(apiBundle.getURL()),
+                bundle(implBundle.getURL())
+        };
+    }
+
+    private boolean isNotReleaseArtifact(final String name) {
+        return name.endsWith(".jar") && !name.contains("-sources") && !name.contains("javadoc");
+    }
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactoryTest.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactoryTest.java
new file mode 100644
index 0000000..03bcc3f
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/runtime/proxy/AsmProxyFactoryTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.service.interceptor.impl.runtime.proxy;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+public class AsmProxyFactoryTest {
+    @Test
+    public void proxy() {
+        final ProxyFactory.ProxyClassLoader classLoader = new ProxyFactory.ProxyClassLoader(Thread.currentThread().getContextClassLoader(), null);
+        final AsmProxyFactory factory = new AsmProxyFactory();
+        final Class<?> proxyClass = factory.createProxyClass(
+                classLoader, Foo.class.getName() + "$$ProxyTestProxy1",
+                new Class<?>[]{Foo.class},
+                Foo.class.getDeclaredMethods());
+        assertNotNull(proxyClass);
+
+        final Foo instance = Foo.class.cast(factory.create(proxyClass, (method, args) -> {
+            switch (method.getName()) {
+                case "fail":
+                    throw new IOException("it must be a checked exception to ensure it is well propagated");
+                default:
+                    return method.getName() + "(" + asList(args) + ")";
+            }
+        }));
+        assertEquals("foo1([])", instance.foo1());
+        assertEquals("foo2([param])", instance.foo2("param"));
+        assertTrue(instance.toString().startsWith(Foo.class.getName() + "$$ProxyTestProxy1@"));
+        try {
+            instance.fail();
+            fail();
+        } catch (final IOException e) {
+            assertEquals("it must be a checked exception to ensure it is well propagated", e.getMessage());
+        }
+    }
+
+    public static class Foo {
+        public String foo1() {
+            return "first";
+        }
+
+        public String foo2(final String some) {
+            return "second<" + some + ">";
+        }
+
+        public String fail() throws IOException {
+            return "ok";
+        }
+    }
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/InterceptedService.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/InterceptedService.java
new file mode 100644
index 0000000..6d5eaec
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/InterceptedService.java
@@ -0,0 +1,34 @@
+/*
+ * 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.service.interceptor.impl.test;
+
+import org.apache.karaf.service.interceptor.api.EnableInterceptors;
+import org.osgi.service.component.annotations.Component;
+
+@Wrap
+@EnableInterceptors
+@Component(service = InterceptedService.class)
+public class InterceptedService {
+    @Suffix
+    public String wrapAndSuffix(final String value) {
+        return "'" + value + "'";
+    }
+
+    public String wrap() {
+        return "from '" + getClass().getName() + "'";
+    }
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Suffix.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Suffix.java
new file mode 100644
index 0000000..d62b735
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Suffix.java
@@ -0,0 +1,32 @@
+/*
+ * 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.service.interceptor.impl.test;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.apache.karaf.service.interceptor.api.InterceptorBinding;
+
+@Target({TYPE, METHOD})
+@Retention(RUNTIME)
+@InterceptorBinding
+public @interface Suffix {
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/SuffixingInterceptor.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/SuffixingInterceptor.java
new file mode 100644
index 0000000..4418a2e
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/SuffixingInterceptor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.service.interceptor.impl.test;
+
+import org.apache.karaf.service.interceptor.api.AroundInvoke;
+import org.apache.karaf.service.interceptor.api.Interceptor;
+import org.apache.karaf.service.interceptor.api.InvocationContext;
+import org.osgi.service.component.annotations.Component;
+
+@Suffix
+@Interceptor
+@Component(service = SuffixingInterceptor.class)
+public class SuffixingInterceptor {
+    @AroundInvoke
+    public Object around(final InvocationContext context) throws Exception {
+        return context.proceed() + "(suffixed)";
+    }
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Wrap.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Wrap.java
new file mode 100644
index 0000000..6280fdf
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/Wrap.java
@@ -0,0 +1,31 @@
+/*
+ * 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.service.interceptor.impl.test;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import org.apache.karaf.service.interceptor.api.InterceptorBinding;
+
+@Target(TYPE)
+@Retention(RUNTIME)
+@InterceptorBinding
+public @interface Wrap {
+}
diff --git a/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/WrappingInterceptor.java b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/WrappingInterceptor.java
new file mode 100644
index 0000000..0653c0e
--- /dev/null
+++ b/services/interceptor/impl/src/test/java/org/apache/karaf/service/interceptor/impl/test/WrappingInterceptor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.service.interceptor.impl.test;
+
+import org.apache.karaf.service.interceptor.api.AroundInvoke;
+import org.apache.karaf.service.interceptor.api.Interceptor;
+import org.apache.karaf.service.interceptor.api.InvocationContext;
+import org.osgi.service.component.annotations.Component;
+
+@Wrap
+@Interceptor
+@Component(service = WrappingInterceptor.class)
+public class WrappingInterceptor {
+    @AroundInvoke
+    public Object around(final InvocationContext context) throws Exception {
+        return "wrapped>" + context.proceed() + "<";
+    }
+}
diff --git a/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.InterceptedService.xml b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.InterceptedService.xml
new file mode 100644
index 0000000..919a970
--- /dev/null
+++ b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.InterceptedService.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="org.apache.karaf.service.interceptor.impl.test.InterceptedService">
+  <property name="apache.karaf.enable.interceptors" type="Boolean" value="true"/>
+  <implementation class="org.apache.karaf.service.interceptor.impl.test.InterceptedService" />
+  <service>
+    <scr:provide interface="org.apache.karaf.service.interceptor.impl.test.InterceptedService"/>
+  </service>
+</scr:component>
diff --git a/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor.xml b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor.xml
new file mode 100644
index 0000000..89d057e
--- /dev/null
+++ b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor">
+  <property name="apache.karaf.interceptor" type="Boolean" value="true"/>
+  <implementation class="org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor"/>
+  <service>
+    <scr:provide interface="org.apache.karaf.service.interceptor.impl.test.SuffixingInterceptor"/>
+  </service>
+</scr:component>
diff --git a/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor.xml b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor.xml
new file mode 100644
index 0000000..47e8071
--- /dev/null
+++ b/services/interceptor/impl/src/test/resources/OSGI-INF/org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" name="org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor">
+  <property name="apache.karaf.interceptor" type="Boolean" value="true"/>
+  <implementation class="org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor"/>
+  <service>
+    <scr:provide interface="org.apache.karaf.service.interceptor.impl.test.WrappingInterceptor"/>
+  </service>
+</scr:component>
diff --git a/services/interceptor/pom.xml b/services/interceptor/pom.xml
new file mode 100644
index 0000000..71035e0
--- /dev/null
+++ b/services/interceptor/pom.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <!--
+
+      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.
+  -->
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.karaf.services</groupId>
+        <artifactId>services</artifactId>
+        <version>4.3.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>org.apache.karaf.services.interceptor</artifactId>
+    <packaging>pom</packaging>
+    <name>Apache Karaf :: Services :: Interceptor</name>
+    <description>Interceptor support (inspired from JavaEE/JakartaEE) on top of SCR.</description>
+
+    <modules>
+        <module>api</module>
+        <module>impl</module>
+    </modules>
+</project>
diff --git a/services/pom.xml b/services/pom.xml
index 6916d86..228fad6 100644
--- a/services/pom.xml
+++ b/services/pom.xml
@@ -37,6 +37,7 @@
         <module>coordinator</module>
         <module>eventadmin</module>
         <module>staticcm</module>
+        <module>interceptor</module>
     </modules>
 
 </project>
diff --git a/shell/console/pom.xml b/shell/console/pom.xml
index 011d92c..af06bba 100644
--- a/shell/console/pom.xml
+++ b/shell/console/pom.xml
@@ -101,7 +101,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
         </dependency>
     </dependencies>
 
diff --git a/shell/core/pom.xml b/shell/core/pom.xml
index 5c3751b..351caab 100644
--- a/shell/core/pom.xml
+++ b/shell/core/pom.xml
@@ -84,7 +84,7 @@
         </dependency>
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
         </dependency>
         <dependency>
             <groupId>org.apache.karaf.service</groupId>
diff --git a/shell/ssh/pom.xml b/shell/ssh/pom.xml
index fc38bdd..81f9184 100644
--- a/shell/ssh/pom.xml
+++ b/shell/ssh/pom.xml
@@ -71,7 +71,15 @@
 
         <dependency>
             <groupId>org.apache.sshd</groupId>
-            <artifactId>sshd-core</artifactId>
+            <artifactId>sshd-osgi</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-sftp</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-scp</artifactId>
         </dependency>
 
         <dependency>
@@ -111,9 +119,9 @@
         </dependency>
 
         <dependency>
-            <groupId>org.apache.mina</groupId>
-            <artifactId>mina-core</artifactId>
-            <version>2.0.15</version>
+            <groupId>org.apache.sshd</groupId>
+            <artifactId>sshd-mina</artifactId>
+            <version>${sshd.version}</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
index 1b22357..70e189a 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/Activator.java
@@ -186,11 +186,11 @@
         server.setShellFactory(new ShellFactoryImpl(sessionFactory));
 
         if (sftpEnabled) {
-            server.setCommandFactory(new ScpCommandFactory.Builder().withDelegate(cmd -> new ShellCommand(sessionFactory, cmd)).build());
+            server.setCommandFactory(new ScpCommandFactory.Builder().withDelegate((channel, cmd) -> new ShellCommand(sessionFactory, cmd)).build());
             server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
             server.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(System.getProperty("karaf.base"))));
         } else {
-            server.setCommandFactory(cmd -> new ShellCommand(sessionFactory, cmd));
+            server.setCommandFactory((channel, cmd) -> new ShellCommand(sessionFactory, cmd));
         }
         server.setKeyPairProvider(keyPairProvider);
         server.setPasswordAuthenticator(authenticator);
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
index 9b4884e..9d7b412 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/KarafAgentFactory.java
@@ -33,6 +33,7 @@
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.channel.Channel;
+import org.apache.sshd.common.channel.ChannelFactory;
 import org.apache.sshd.common.session.ConnectionService;
 import org.apache.sshd.common.session.Session;
 import org.apache.sshd.server.session.ServerSession;
@@ -53,7 +54,7 @@
     }
 
     @Override
-    public List<NamedFactory<Channel>> getChannelForwardingFactories(FactoryManager factoryManager) {
+    public List<ChannelFactory> getChannelForwardingFactories(FactoryManager factoryManager) {
         return LocalAgentFactory.DEFAULT_FORWARDING_CHANNELS;
     }
 
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
index d72a278..55edafc 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellCommand.java
@@ -36,15 +36,15 @@
 import org.apache.karaf.util.StreamUtils;

 import org.apache.karaf.util.filesstream.FilesStream;

 import org.apache.karaf.util.jaas.JaasHelper;

-import org.apache.sshd.server.Command;

 import org.apache.sshd.server.Environment;

 import org.apache.sshd.server.ExitCallback;

-import org.apache.sshd.server.SessionAware;

+import org.apache.sshd.server.channel.ChannelSession;

+import org.apache.sshd.server.command.Command;

 import org.apache.sshd.server.session.ServerSession;

 import org.slf4j.Logger;

 import org.slf4j.LoggerFactory;

 

-public class ShellCommand implements Command, SessionAware {

+public class ShellCommand implements Command {

 

     public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";

     public static final String EXEC_INIT_SCRIPT = "karaf.exec.init.script";

@@ -87,12 +87,10 @@
         this.callback = callback;

     }

 

-    public void setSession(ServerSession session) {

-        this.session = session;

-    }

-

-    public void start(final Environment env) throws IOException {

-        this.env = env;

+    @Override

+    public void start(ChannelSession channelSession, Environment environment) throws IOException {

+        this.session = channelSession.getServerSession();

+        this.env = environment;

         new Thread(this::run).start();

     }

 

@@ -150,7 +148,9 @@
         }

     }

 

-    public void destroy() {

+    @Override

+    public void destroy(ChannelSession channelSession) throws Exception {

+

     }

 

     private void executeScript(String names, Session session) {

diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
index 4ee0b32..4a69cb4 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/ShellFactoryImpl.java
@@ -32,29 +32,31 @@
 import org.apache.karaf.shell.api.console.SessionFactory;
 import org.apache.karaf.shell.support.ShellUtil;
 import org.apache.karaf.util.jaas.JaasHelper;
-import org.apache.sshd.common.Factory;
-import org.apache.sshd.server.Command;
 import org.apache.sshd.server.Environment;
 import org.apache.sshd.server.ExitCallback;
-import org.apache.sshd.server.SessionAware;
+import org.apache.sshd.server.channel.ChannelSession;
+import org.apache.sshd.server.command.Command;
 import org.apache.sshd.server.session.ServerSession;
+import org.apache.sshd.server.shell.ShellFactory;
 
 /**
- * SSHD {@link org.apache.sshd.server.Command} factory which provides access to
+ * SSHD {@link org.apache.sshd.server.command.Command} factory which provides access to
  * Shell.
  */
-public class ShellFactoryImpl implements Factory<Command> {
+public class ShellFactoryImpl implements ShellFactory {
+
     private SessionFactory sessionFactory;
 
     public ShellFactoryImpl(SessionFactory sessionFactory) {
         this.sessionFactory = sessionFactory;
     }
 
-    public Command create() {
+    @Override
+    public Command createShell(ChannelSession channelSession) {
         return new ShellImpl();
     }
 
-    public class ShellImpl implements Command, SessionAware {
+    public class ShellImpl implements Command {
         private InputStream in;
 
         private OutputStream out;
@@ -71,42 +73,43 @@
 
         private boolean closed;
 
+        @Override
         public void setInputStream(final InputStream in) {
             this.in = in;
         }
 
+        @Override
         public void setOutputStream(final OutputStream out) {
             this.out = out;
         }
 
+        @Override
         public void setErrorStream(final OutputStream err) {
             this.err = err;
         }
 
+        @Override
         public void setExitCallback(ExitCallback callback) {
             this.callback = callback;
         }
 
-        public void setSession(ServerSession session) {
-            this.session = session;
-        }
-
-        public void start(final Environment env) throws IOException {
+        @Override
+        public void start(ChannelSession channelSession, Environment environment) throws IOException {
+            this.session = channelSession.getServerSession();
             try {
-                final Subject subject = ShellImpl.this.session != null ? ShellImpl.this.session
-                        .getAttribute(KarafJaasAuthenticator.SUBJECT_ATTRIBUTE_KEY) : null;
-                String encoding = getEncoding(env);
-                terminal = new SshTerminal(env, in, out, encoding);
+                final Subject subject = session.getAttribute(KarafJaasAuthenticator.SUBJECT_ATTRIBUTE_KEY);
+                String encoding = getEncoding(environment);
+                terminal = new SshTerminal(environment, in, out, encoding);
                 final PrintStream pout = new PrintStream(terminal.output(), true, encoding);
                 final PrintStream perr = err instanceof PrintStream ?
                         (PrintStream) err : out == err ? pout : new PrintStream(err, true, encoding);
                 shell = sessionFactory.create(in, pout,
                         perr, terminal, encoding, this::destroy);
-                for (Map.Entry<String, String> e : env.getEnv().entrySet()) {
+                for (Map.Entry<String, String> e : environment.getEnv().entrySet()) {
                     shell.put(e.getKey(), e.getValue());
                 }
                 JaasHelper.runAs(subject, () ->
-                    new Thread(shell, "Karaf ssh console user " + ShellUtil.getCurrentUserName()).start());
+                        new Thread(shell, "Karaf ssh console user " + ShellUtil.getCurrentUserName()).start());
             } catch (Exception e) {
                 throw new IOException("Unable to start shell", e);
             }
@@ -115,12 +118,14 @@
         public void destroy() {
             if (!closed) {
                 closed = true;
-                flush(out, err);
-                close(in, out, err);
                 callback.onExit(0);
             }
         }
 
+        @Override
+        public void destroy(ChannelSession channelSession) {
+            destroy();
+        }
     }
 
     /**
@@ -158,24 +163,4 @@
         return null;
     }
 
-    private static void flush(OutputStream... streams) {
-        for (OutputStream s : streams) {
-            try {
-                s.flush();
-            } catch (IOException e) {
-                // Ignore
-            }
-        }
-    }
-
-    private static void close(Closeable... closeables) {
-        for (Closeable c : closeables) {
-            try {
-                c.close();
-            } catch (Exception e) {
-                // Ignore
-            }
-        }
-    }
-
 }
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
index 0534b1b..694b8e8 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshAction.java
@@ -123,7 +123,7 @@
         KnownHostsManager knownHostsManager = new KnownHostsManager(new File(System.getProperty("user.home"), ".sshkaraf/known_hosts"));
         ServerKeyVerifier serverKeyVerifier = new ServerKeyVerifierImpl(knownHostsManager, quiet);
         client.setServerKeyVerifier(serverKeyVerifier);
-        client.setKeyPairProvider(new FileKeyPairProvider());
+        client.setKeyIdentityProvider(new FileKeyPairProvider());
         log.debug("Created client: {}", client);
         client.setUserInteraction(new UserInteraction() {
             @Override
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
index 62b8728..38c4028 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshTerminal.java
@@ -135,7 +135,7 @@
         setSize(new Size(w, h));
     }
 
-    protected void handleSignal(org.apache.sshd.server.Signal signal) {
+    protected void handleSignal(org.apache.sshd.common.channel.Channel channel, org.apache.sshd.server.Signal signal) {
         if (signal == org.apache.sshd.server.Signal.INT) {
             raise(Signal.INT);
         } else if (signal == org.apache.sshd.server.Signal.QUIT) {
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
index 5542f08..762bdec 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/SshUtils.java
@@ -22,6 +22,7 @@
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.server.ServerBuilder;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.common.NamedFactory;
@@ -57,6 +58,25 @@
         return list;
     }
 
+    public static List<KeyExchangeFactory> filter(List<KeyExchangeFactory> factories, String[] names) {
+        List<KeyExchangeFactory> list = new ArrayList<>();
+        for (String name : names) {
+            name = name.trim();
+            boolean found = false;
+            for (KeyExchangeFactory factory : factories) {
+                if (factory.getName().equals(name)) {
+                    list.add(factory);
+                    found = true;
+                    break;
+                }
+            }
+            if (!found) {
+                LOGGER.warn("Configured KeyExchangeFactory '" + name + "' not available");
+            }
+        }
+        return list;
+    }
+
     public static List<NamedFactory<Mac>> buildMacs(String[] names) {
         return filter(Mac.class, new ServerConfig().getMacFactories(), names);
     }
@@ -67,11 +87,11 @@
         return filter(Cipher.class, avail, names);
     }
 
-    public static List<NamedFactory<KeyExchange>> buildKexAlgorithms(String[] names) {
+    public static List<KeyExchangeFactory> buildKexAlgorithms(String[] names) {
         ServerConfig defaults = new ServerConfig();
-        List<NamedFactory<KeyExchange>> avail = defaults.getKeyExchangeFactories();
+        List<KeyExchangeFactory> avail = defaults.getKeyExchangeFactories();
 
-        return filter(KeyExchange.class, avail, names);
+        return filter(avail, names);
     }
 
     /**
@@ -108,7 +128,7 @@
             return null;
         }
 
-        public List<NamedFactory<KeyExchange>> getKeyExchangeFactories() {
+        public List<KeyExchangeFactory> getKeyExchangeFactories() {
             return keyExchangeFactories;
          }
  
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
index 4d59e73..fcd45be 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/UserAuthFactoriesFactory.java
@@ -23,8 +23,7 @@
 import java.util.List;
 import java.util.Set;
 
-import org.apache.sshd.common.NamedFactory;
-import org.apache.sshd.server.auth.UserAuth;
+import org.apache.sshd.server.auth.UserAuthFactory;
 import org.apache.sshd.server.auth.keyboard.UserAuthKeyboardInteractiveFactory;
 import org.apache.sshd.server.auth.password.UserAuthPasswordFactory;
 import org.apache.sshd.server.auth.pubkey.UserAuthPublicKeyFactory;
@@ -47,7 +46,7 @@
     public static final String KEYBOARD_INTERACTIVE_METHOD = "keyboard-interactive";
 
     private Set<String> methodSet;
-    private List<NamedFactory<UserAuth>> factories;
+    private List<UserAuthFactory> factories;
 
    public void setAuthMethods(String[] methods) {
         this.methodSet = new HashSet<>();
@@ -66,7 +65,7 @@
         }
     }
 
-    public List<NamedFactory<UserAuth>> getFactories() {
+    public List<UserAuthFactory> getFactories() {
         return factories;
     }
 
diff --git a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHKeyPairProvider.java b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHKeyPairProvider.java
index 792ec6b..510101f 100644
--- a/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHKeyPairProvider.java
+++ b/shell/ssh/src/main/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHKeyPairProvider.java
@@ -40,10 +40,12 @@
 import java.util.Base64;
 
 import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
+import org.apache.sshd.common.session.SessionContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 public class OpenSSHKeyPairProvider extends AbstractKeyPairProvider {
+
     private static final Logger LOGGER = LoggerFactory.getLogger(OpenSSHKeyPairProvider.class);
     private Path privateKeyPath;
     private Path publicKeyPath;
@@ -61,7 +63,7 @@
     }
 
     @Override
-    public synchronized Iterable<KeyPair> loadKeys() {
+    public synchronized Iterable<KeyPair> loadKeys(SessionContext sessionContext) throws IOException, GeneralSecurityException {
         if (cachedKey != null) {
             return singleton(cachedKey);
         }
diff --git a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticatorTest.java b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticatorTest.java
index 5d9b450..972f914 100644
--- a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticatorTest.java
+++ b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/KarafJaasAuthenticatorTest.java
@@ -36,10 +36,16 @@
 
 import org.apache.karaf.jaas.boot.principal.RolePrincipal;
 import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.mina.core.service.IoProcessor;
 import org.apache.mina.core.session.DummySession;
+import org.apache.mina.core.write.WriteRequest;
+import org.apache.sshd.common.io.IoHandler;
+import org.apache.sshd.common.io.IoSession;
 import org.apache.sshd.common.io.mina.MinaConnector;
 import org.apache.sshd.common.io.mina.MinaSession;
 import org.apache.sshd.common.random.SingletonRandomFactory;
+import org.apache.sshd.common.util.Readable;
+import org.apache.sshd.common.util.net.SshdSocketAddress;
 import org.apache.sshd.common.util.security.SecurityUtils;
 import org.apache.sshd.server.SshServer;
 import org.apache.sshd.server.session.ServerSessionImpl;
@@ -48,6 +54,7 @@
 import org.junit.Test;
 
 public class KarafJaasAuthenticatorTest {
+
     private Configuration configuration;
     private ServerSessionImpl session;
 
@@ -63,9 +70,70 @@
             }
         });
         final SshServer server = new SshServer();
+        IoHandler ioHandler = new IoHandler() {
+            @Override
+            public void sessionCreated(IoSession ioSession) throws Exception {
+
+            }
+
+            @Override
+            public void sessionClosed(IoSession ioSession) throws Exception {
+
+            }
+
+            @Override
+            public void exceptionCaught(IoSession ioSession, Throwable throwable) throws Exception {
+
+            }
+
+            @Override
+            public void messageReceived(IoSession ioSession, Readable readable) throws Exception {
+
+            }
+        };
+        IoProcessor ioProcessor = new IoProcessor() {
+            @Override
+            public boolean isDisposing() {
+                return false;
+            }
+
+            @Override
+            public boolean isDisposed() {
+                return false;
+            }
+
+            @Override
+            public void dispose() {
+
+            }
+
+            @Override
+            public void add(org.apache.mina.core.session.IoSession ioSession) {
+
+            }
+
+            @Override
+            public void flush(org.apache.mina.core.session.IoSession ioSession) {
+
+            }
+
+            @Override
+            public void write(org.apache.mina.core.session.IoSession ioSession, WriteRequest writeRequest) {
+
+            }
+
+            @Override
+            public void updateTrafficControl(org.apache.mina.core.session.IoSession ioSession) {
+
+            }
+
+            @Override
+            public void remove(org.apache.mina.core.session.IoSession ioSession) {
+
+            }
+        };
         server.setRandomFactory(new SingletonRandomFactory(SecurityUtils.getRandomFactory()));
-        this.session = new ServerSessionImpl(server,
-                new MinaSession(new MinaConnector(null, null, null), new DummySession()));
+        this.session = new ServerSessionImpl(server, new MinaSession(new MinaConnector(server, ioHandler, ioProcessor), new DummySession(), SshdSocketAddress.LOCALHOST_ADDRESS));
     }
 
     @After
diff --git a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
index eda708c..c463b6a 100644
--- a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
+++ b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/SshUtilsTest.java
@@ -22,6 +22,7 @@
 import java.util.List;
 import org.apache.sshd.common.cipher.Cipher;
 import org.apache.sshd.common.kex.KeyExchange;
+import org.apache.sshd.common.kex.KeyExchangeFactory;
 import org.apache.sshd.common.mac.Mac;
 import org.apache.sshd.common.NamedFactory;
 
@@ -82,12 +83,12 @@
         // verify our default configuration...
         String kexAlgorithms = "diffie-hellman-group-exchange-sha256,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha1,diffie-hellman-group1-sha1";
 
-        List<NamedFactory<KeyExchange>> list = SshUtils.buildKexAlgorithms(kexAlgorithms.split(","));
+        List<KeyExchangeFactory> list = SshUtils.buildKexAlgorithms(kexAlgorithms.split(","));
 
         // verify that all configured key exchange algorithms are actually resolved...
         for (String kex : kexAlgorithms.split(",")) {
             boolean found = false;
-            for (NamedFactory<KeyExchange> factory : list) {
+            for (KeyExchangeFactory factory : list) {
                 if (factory.getName().equalsIgnoreCase(kex)) {
                     found = true;
                     break;
diff --git a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHGeneratorKeyFileProviderTest.java b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHGeneratorKeyFileProviderTest.java
index bd2200f..a08f6ab 100644
--- a/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHGeneratorKeyFileProviderTest.java
+++ b/shell/ssh/src/test/java/org/apache/karaf/shell/ssh/keygenerator/OpenSSHGeneratorKeyFileProviderTest.java
@@ -48,10 +48,9 @@
         KeyPair kp = new OpenSSHKeyPairGenerator(KeyUtils.RSA_ALGORITHM, 1024).generate();
         new PemWriter(privateKeyTemp.toPath(), publicKeyTemp.toPath()).writeKeyPair(KeyUtils.RSA_ALGORITHM, kp);
 
-        //File path = new File("/home/cschneider/.ssh/id_rsa");
         OpenSSHKeyPairProvider prov =
             new OpenSSHKeyPairProvider(privateKeyTemp.toPath(), publicKeyTemp.toPath(), KeyUtils.RSA_ALGORITHM, 1024, null);
-        KeyPair keys = prov.loadKeys().iterator().next();
+        KeyPair keys = prov.loadKeys(null).iterator().next();
         Assert.assertNotNull(keys);
         Assert.assertTrue("Loaded key is not RSA Key", keys.getPrivate() instanceof RSAPrivateCrtKey);
         Assert.assertTrue("Loaded key is not RSA Key", keys.getPublic() instanceof RSAPublicKey);
@@ -64,17 +63,17 @@
         File publicKeyTemp = File.createTempFile(this.getClass().getCanonicalName(), ".pub");
         publicKeyTemp.deleteOnExit();
 
-        SimpleGeneratorHostKeyProvider simpleGenerator = new SimpleGeneratorHostKeyProvider(privateKeyTemp);
+        SimpleGeneratorHostKeyProvider simpleGenerator = new SimpleGeneratorHostKeyProvider(privateKeyTemp.toPath());
         simpleGenerator.setKeySize(2048);
         simpleGenerator.setAlgorithm("DSA");
-        List<KeyPair> keys = simpleGenerator.loadKeys();
+        List<KeyPair> keys = simpleGenerator.loadKeys(null);
         KeyPair simpleKeyPair = keys.stream().findFirst().get();
 
         Assert.assertEquals("DSA", simpleKeyPair.getPrivate().getAlgorithm());
 
         OpenSSHKeyPairProvider provider =
             new OpenSSHKeyPairProvider(privateKeyTemp.toPath(), publicKeyTemp.toPath(), "DSA", 2048, null);
-        KeyPair convertedKeyPair = provider.loadKeys().iterator().next();
+        KeyPair convertedKeyPair = provider.loadKeys(null).iterator().next();
         Assert.assertEquals("DSA", convertedKeyPair.getPrivate().getAlgorithm());
 
         Assert.assertArrayEquals(simpleKeyPair.getPrivate().getEncoded(),convertedKeyPair.getPrivate().getEncoded());
@@ -97,7 +96,7 @@
 
         OpenSSHKeyPairProvider prov =
             new OpenSSHKeyPairProvider(privateKeyTemp.toPath(), publicKeyTemp.toPath(), KeyUtils.EC_ALGORITHM, 256, null);
-        KeyPair keys = prov.loadKeys().iterator().next();
+        KeyPair keys = prov.loadKeys(null).iterator().next();
         Assert.assertNotNull(keys);
         Assert.assertTrue("Loaded key is not EC Key", keys.getPrivate() instanceof ECPrivateKey);
         Assert.assertTrue("Loaded key is not EC Key", keys.getPublic() instanceof ECPublicKey);
@@ -111,7 +110,7 @@
         OpenSSHKeyPairProvider prov =
             new OpenSSHKeyPairProvider(privateKeyPath, null, KeyUtils.RSA_ALGORITHM, 1024, null);
         try {
-            prov.loadKeys();
+            prov.loadKeys(null);
             fail("Failure expected on a decryption failure");
         } catch (Exception ex) {
             // expected
@@ -120,7 +119,7 @@
         // Now we provide the wrong password
         prov = new OpenSSHKeyPairProvider(privateKeyPath, null, KeyUtils.RSA_ALGORITHM, 1024, "password");
         try {
-            prov.loadKeys();
+            prov.loadKeys(null);
             fail("Failure expected on a decryption failure");
         } catch (Exception ex) {
             // expected
@@ -128,7 +127,7 @@
 
         // Now it should work
         prov = new OpenSSHKeyPairProvider(privateKeyPath, null, KeyUtils.RSA_ALGORITHM, 1024, "security");
-        KeyPair keys = prov.loadKeys().iterator().next();
+        KeyPair keys = prov.loadKeys(null).iterator().next();
         Assert.assertNotNull(keys);
         Assert.assertTrue("Loaded key is not RSA Key", keys.getPrivate() instanceof RSAPrivateCrtKey);
         Assert.assertTrue("Loaded key is not RSA Key", keys.getPublic() instanceof RSAPublicKey);
diff --git a/tooling/karaf-maven-plugin/pom.xml b/tooling/karaf-maven-plugin/pom.xml
index 893159d..4b07966 100644
--- a/tooling/karaf-maven-plugin/pom.xml
+++ b/tooling/karaf-maven-plugin/pom.xml
@@ -134,9 +134,18 @@
                     <groupId>org.osgi</groupId>
                     <artifactId>org.osgi.core</artifactId>
                 </exclusion>
+                <exclusion>
+                    <groupId>org.apache.maven</groupId>
+                    <artifactId>maven-archiver</artifactId>
+                </exclusion>
             </exclusions>
         </dependency>
         <dependency>
+            <groupId>org.apache.maven</groupId>
+            <artifactId>maven-archiver</artifactId>
+            <version>2.6</version>
+        </dependency>
+        <dependency>
             <groupId>org.codehaus.plexus</groupId>
             <artifactId>plexus-archiver</artifactId>
             <version>3.6.0</version>
@@ -342,7 +351,7 @@
             <plugin>
                 <groupId>org.jacoco</groupId>
                 <artifactId>jacoco-maven-plugin</artifactId>
-                <version>0.8.1</version>
+                <version>0.8.5</version>
                 <executions>
                     <execution>
                         <goals>
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/DockerfileMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/DockerfileMojo.java
index aeaa324..8b8de7b 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/DockerfileMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/DockerfileMojo.java
@@ -49,6 +49,7 @@
             buffer.append("FROM openjdk:8-jre").append("\n");
             buffer.append("ENV KARAF_INSTALL_PATH /opt").append("\n");
             buffer.append("ENV KARAF_HOME $KARAF_INSTALL_PATH/apache-karaf").append("\n");
+            buffer.append("ENV KARAF_EXEC exec").append("\n");
             buffer.append("ENV PATH $PATH:$KARAF_HOME/bin").append("\n");
             buffer.append("COPY ").append(assembly.getName()).append(" $KARAF_HOME").append("\n");
             buffer.append("EXPOSE 8101 1099 44444 8181").append("\n");
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
index 0b2238f..17b8d0d 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/VerifyMojo.java
@@ -608,6 +608,12 @@
                 downloader.download(repository, new DownloadCallback() {
                     @Override
                     public void downloaded(final StreamProvider provider) throws Exception {
+                        synchronized (loaded) {
+                            // If provider was already loaded, no need to do it again.
+                            if (loaded.containsKey(provider.getUrl())) {
+                                return;
+                            }
+                        }
                         try (InputStream is = provider.open()) {
                             Features featuresModel = JaxbUtil.unmarshal(provider.getUrl(), is, false);
                             processor.process(featuresModel);
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
index d074167..6998abc 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/ClientMojo.java
@@ -250,7 +250,7 @@
             SshAgent agent = new AgentImpl();
             if (keyFile != null) {
                 FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(keyFile.getAbsoluteFile().toPath());
-                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                for (KeyPair key : fileKeyPairProvider.loadKeys(null)) {
                     agent.addIdentity(key, user);
                 }
             }
diff --git a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
index 92312eb..b403e49 100644
--- a/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
+++ b/tooling/karaf-maven-plugin/src/main/java/org/apache/karaf/tooling/client/DeployMojo.java
@@ -247,7 +247,7 @@
             SshAgent agent = new AgentImpl();
             if (keyFile != null) {
                 FileKeyPairProvider fileKeyPairProvider = new FileKeyPairProvider(keyFile.getAbsoluteFile().toPath());
-                for (KeyPair key : fileKeyPairProvider.loadKeys()) {
+                for (KeyPair key : fileKeyPairProvider.loadKeys(null)) {
                     agent.addIdentity(key, user);
                 }
             }
diff --git a/tooling/karaf-services-maven-plugin/src/main/java/org/apache/karaf/tooling/tracker/GenerateServiceMetadata.java b/tooling/karaf-services-maven-plugin/src/main/java/org/apache/karaf/tooling/tracker/GenerateServiceMetadata.java
index d2fd670..1790330 100644
--- a/tooling/karaf-services-maven-plugin/src/main/java/org/apache/karaf/tooling/tracker/GenerateServiceMetadata.java
+++ b/tooling/karaf-services-maven-plugin/src/main/java/org/apache/karaf/tooling/tracker/GenerateServiceMetadata.java
@@ -140,7 +140,7 @@
             List<Class<?>> services = finder.findAnnotatedClasses(Service.class);
             Set<String> packages = new TreeSet<>();
             for (Class<?> clazz : services) {
-                getLog().info("Service " + clazz.getPackage().getName());
+                getLog().info("Service " + clazz.getCanonicalName());
                 packages.add(clazz.getPackage().getName());
             }
             if (!packages.isEmpty()) {
@@ -196,14 +196,15 @@
 
             urls.add(new File(project.getBuild().getOutputDirectory()).toURI().toURL());
             for (Artifact artifact : project.getArtifacts()) {
-                if (artifactInclude != null && artifactInclude.length() > 0 && artifact.getArtifactId().matches(artifactInclude)) {
+                String name = artifact.getGroupId() + ":" + artifact.getArtifactId();
+                if (artifactInclude != null && artifactInclude.length() > 0 && name.matches(artifactInclude)) {
                     File file = artifact.getFile();
                     if (file != null) {
-                        getLog().debug("Use artifact " + artifact.getArtifactId() + ": " + file);
+                        getLog().debug("Use artifact " + name + " " + file);
                         urls.add(file.toURI().toURL());
                     }
                 } else {
-                    getLog().debug("Ignore artifact " + artifact.getArtifactId());
+                    getLog().debug("Ignore artifact " + name);
                 }
             }
             ClassLoader loader = new URLClassLoader(urls.toArray(new URL[urls.size()]), getClass().getClassLoader());
diff --git a/tooling/utils/pom.xml b/tooling/utils/pom.xml
index 85fc20b..d91c442 100644
--- a/tooling/utils/pom.xml
+++ b/tooling/utils/pom.xml
@@ -48,7 +48,7 @@
             <plugin>
                 <groupId>org.codehaus.modello</groupId>
                 <artifactId>modello-maven-plugin</artifactId>
-                <version>1.9.1</version>
+                <version>1.11</version>
                 <executions>
                     <execution>
                         <phase>generate-sources</phase>
diff --git a/util/src/main/java/org/apache/karaf/jpm/impl/ProcessImpl.java b/util/src/main/java/org/apache/karaf/jpm/impl/ProcessImpl.java
index c79e8ce..6b122a8 100644
--- a/util/src/main/java/org/apache/karaf/jpm/impl/ProcessImpl.java
+++ b/util/src/main/java/org/apache/karaf/jpm/impl/ProcessImpl.java
@@ -60,11 +60,11 @@
             return ret == 0;
         } else {
             try {
-                java.lang.Process process = new java.lang.ProcessBuilder("ps", "-p", Integer.toString(pid)).start();
+                java.lang.Process process = new java.lang.ProcessBuilder("ps", "-o", "stat", "-p", Integer.toString(pid)).start();
                 try (BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
                     r.readLine(); // skip headers
                     String s = r.readLine();
-                    boolean running = s != null && s.length() > 0;
+                    boolean running = s != null && s.length() > 0 && s.indexOf("Z") < 0;
                     process.waitFor();
                     return running;
                 }
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper-java11.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper-java11.conf
index dcb0db2..6952dfe 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper-java11.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/unix/karaf-wrapper-java11.conf
@@ -30,7 +30,7 @@
 # Include JAVA_HOME/bin in the default PATH variable
 set.PATH=%PATH_WITH_JAVA%
 
-set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
+set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
 
 # Java Application
 wrapper.working.dir=%KARAF_BASE%
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper-java11.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper-java11.conf
index 22d080b..7d2c722 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper-java11.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows/karaf-wrapper-java11.conf
@@ -30,7 +30,7 @@
 # Include JAVA_HOME/bin in the default PATH variable
 set.PATH=%PATH_WITH_JAVA%
 
-set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
+set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
 
 # Java Application
 wrapper.working.dir=%KARAF_BASE%
diff --git a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper-java11.conf b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper-java11.conf
index 35d4e93..d5a4534 100644
--- a/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper-java11.conf
+++ b/wrapper/src/main/resources/org/apache/karaf/wrapper/internal/windows64/karaf-wrapper-java11.conf
@@ -30,7 +30,7 @@
 # Include JAVA_HOME/bin in the default PATH variable
 set.PATH=%PATH_WITH_JAVA%
 
-set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
+set.JDK_JAVA_OPTIONS=--add-reads=java.xml=java.logging --add-exports=java.base/org.apache.karaf.specs.locator=java.xml,ALL-UNNAMED --patch-module java.base=lib/endorsed/org.apache.karaf.specs.locator-%KARAF_VERSION%.jar --patch-module java.xml=lib/endorsed/org.apache.karaf.specs.java.xml-%KARAF_VERSION%.jar --add-opens java.base/java.security=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.naming/javax.naming.spi=ALL-UNNAMED --add-opens java.rmi/sun.rmi.transport.tcp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.file=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.ftp=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.https=ALL-UNNAMED --add-exports=java.base/sun.net.www.protocol.jar=ALL-UNNAMED --add-exports=java.base/sun.net.www.content.text=ALL-UNNAMED --add-exports=jdk.xml.dom/org.w3c.dom.html=ALL-UNNAMED --add-exports=jdk.naming.rmi/com.sun.jndi.url.rmi=ALL-UNNAMED
 
 # Java Application
 wrapper.working.dir=%KARAF_BASE%