You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by ra...@apache.org on 2017/12/03 12:25:01 UTC

[sling-org-apache-sling-scripting-core] 01/01: SLING-7134 - Script execution order is not deterministic on Java 9

This is an automated email from the ASF dual-hosted git repository.

radu pushed a commit to branch issue/SLING-7134
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-scripting-core.git

commit 2e15bd4f230d390ceadc9481627156bb3b6caaa3
Author: Radu Cotescu <ra...@apache.org>
AuthorDate: Sun Dec 3 13:23:55 2017 +0100

    SLING-7134 - Script execution order is not deterministic on Java 9
    
    * reimplemented the former o.a.s.scripting.core.impl.ScriptEngineManagerFactory,
    o.a.s.scripting.core.impl.helper.SlingScriptEngineManager and
    o.a.s.scripting.core.impl.helper.ProxyScriptEngineManager into one service:
    o.a.s.scripting.core.impl.jsr223.SlingScriptEngineManager
    * the new SlingScriptEngineManager manages ScriptEngineFactories registrations
    from the platform's ScriptEngineManager, bundles that expose a ScriptEngineFactory
    through the SPI mechanism and ScriptEngineFactories registered as OSGi services
    * introduced the concept of a SortableScriptEngineFactory, that allows ordering the
    factories based on bundle IDs and service rankings; if two factories have the same service
    ranking, then the factory with the higher bundle ID will win
    * corrected bug in the ScriptCacheImpl to cache scripts from all ScriptEngineFactories that
    register a Compilable ScriptEngine, not just for the ScriptEngineFactories registered as
    OSGi services
    * extended tests
    * updated PAX exam tests to run on Java 9
---
 pom.xml                                            |  21 +-
 .../impl/BindingsValuesProvidersByContextImpl.java |   4 +-
 .../sling/scripting/core/impl/ScriptCacheImpl.java |  52 ++--
 .../core/impl/ScriptEngineConsolePlugin.java       | 109 +++----
 .../core/impl/ScriptEngineManagerFactory.java      | 281 ------------------
 .../core/impl/SlingScriptAdapterFactory.java       |   2 +-
 .../core/impl/helper/ProxyScriptEngineManager.java | 107 -------
 .../core/impl/helper/SlingScriptEngineManager.java |  77 -----
 .../core/impl/jsr223/SlingScriptEngineManager.java | 320 +++++++++++++++++++++
 .../impl/jsr223/SortableScriptEngineFactory.java   | 134 +++++++++
 .../sling/scripting/core/ScriptHelperTest.java     |   6 +-
 .../core/impl/ScriptEngineManagerFactoryTest.java  | 168 -----------
 .../{ => jsr223}/DummyScriptEngineFactory.java     |  36 +--
 .../impl/jsr223/SlingScriptEngineManagerTest.java  | 208 ++++++++++++++
 .../jsr223/SortableScriptEngineFactoryTest.java    | 174 +++++++++++
 .../core/it/ScriptingCoreTestSupport.java          |  16 +-
 16 files changed, 957 insertions(+), 758 deletions(-)

diff --git a/pom.xml b/pom.xml
index a49404b..9166e97 100644
--- a/pom.xml
+++ b/pom.xml
@@ -23,7 +23,7 @@
     <parent>
         <groupId>org.apache.sling</groupId>
         <artifactId>sling</artifactId>
-        <version>30</version>
+        <version>32</version>
         <relativePath />
     </parent>
 
@@ -37,7 +37,7 @@
     </description>
 
     <properties>
-        <org.ops4j.pax.exam.version>4.10.0</org.ops4j.pax.exam.version>
+        <org.ops4j.pax.exam.version>4.11.0</org.ops4j.pax.exam.version>
     </properties>
 
     <scm>
@@ -140,7 +140,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.commons.osgi</artifactId>
-            <version>2.1.0</version>
+            <version>2.4.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -182,7 +182,7 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.webconsole</artifactId>
-            <version>3.1.8</version>
+            <version>4.2.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -221,23 +221,28 @@
         <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.6.2</version>
+            <version>5.6.10</version>
             <scope>test</scope>
         </dependency>
 
-        
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.osgi-mock</artifactId>
-            <version>2.0.2</version>
+            <version>2.3.5-SNAPSHOT</version>
             <scope>test</scope>
         </dependency>
         
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.sling-mock</artifactId>
-            <version>1.6.0</version>
+            <version>2.2.14</version>
             <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.sling</groupId>
+                    <artifactId>org.apache.sling.scripting.core</artifactId>
+                </exclusion>
+            </exclusions>
         </dependency>
 
         <dependency>
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextImpl.java b/src/main/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextImpl.java
index b8fdec5..63f439c 100644
--- a/src/main/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextImpl.java
+++ b/src/main/java/org/apache/sling/scripting/core/impl/BindingsValuesProvidersByContextImpl.java
@@ -34,7 +34,7 @@ import javax.script.ScriptEngineFactory;
 import org.apache.sling.commons.osgi.PropertiesUtil;
 import org.apache.sling.scripting.api.BindingsValuesProvider;
 import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
-import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager;
+import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceReference;
@@ -151,7 +151,7 @@ public class BindingsValuesProvidersByContextImpl implements BindingsValuesProvi
 
         // we load the compatible language ones first so that the most specific
         // overrides these
-        Map<Object, Object> factoryProps = scriptEngineManager.getProperties(scriptEngineFactory);
+        Map<String, Object> factoryProps = scriptEngineManager.getProperties(scriptEngineFactory);
         if (factoryProps != null) {
             String[] compatibleLangs = PropertiesUtil.toStringArray(factoryProps.get("compatible.javax.script.name"), new String[0]);
             for (final String name : compatibleLangs) {
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java
index 013e758..a17e05d 100644
--- a/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java
+++ b/src/main/java/org/apache/sling/scripting/core/impl/ScriptCacheImpl.java
@@ -47,6 +47,7 @@ import org.apache.sling.commons.threads.ThreadPoolManager;
 import org.apache.sling.scripting.api.CachedScript;
 import org.apache.sling.scripting.api.ScriptCache;
 import org.apache.sling.scripting.core.impl.helper.CachingMap;
+import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager;
 import org.apache.sling.serviceusermapping.ServiceUserMapped;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
@@ -57,22 +58,18 @@ import org.osgi.service.component.annotations.Deactivate;
 import org.osgi.service.component.annotations.Reference;
 import org.osgi.service.component.annotations.ReferenceCardinality;
 import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
 import org.osgi.service.metatype.annotations.Designate;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 @Component(
-    service = ScriptCache.class,
-    reference = @Reference(
-            name = "ScriptEngineFactory",
-            bind = "bindScriptEngineFactory",
-            unbind = "unbindScriptEngineFactory",
-            service = ScriptEngineFactory.class,
-            cardinality = ReferenceCardinality.MULTIPLE,
-            policy = ReferencePolicy.DYNAMIC
-    ),
+    service = {ScriptCache.class, EventHandler.class},
     property = {
-            Constants.SERVICE_VENDOR + "=The Apache Software Foundation"
+            Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+            EventConstants.EVENT_TOPIC + "=org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/*"
     }
 )
 @Designate(
@@ -82,7 +79,7 @@ import org.slf4j.LoggerFactory;
  * The {@code ScriptCache} stores information about {@link CompiledScript} instances evaluated by various {@link ScriptEngine}s that
  * implement the {@link Compilable} interface.
  */
-public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, ExternalResourceChangeListener {
+public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, ExternalResourceChangeListener, EventHandler {
 
     private final Logger LOGGER = LoggerFactory.getLogger(ScriptCacheImpl.class);
 
@@ -102,6 +99,9 @@ public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, Ext
     @Reference
     private ThreadPoolManager threadPoolManager;
 
+    @Reference
+    private SlingScriptEngineManager slingScriptEngineManager;
+
     private ThreadPool threadPool;
     private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
     private final Lock readLock = rwl.readLock();
@@ -284,27 +284,17 @@ public class ScriptCacheImpl implements ScriptCache, ResourceChangeListener, Ext
         active = false;
     }
 
-    protected void bindScriptEngineFactory(ScriptEngineFactory scriptEngineFactory) {
-        ScriptEngine engine = scriptEngineFactory.getScriptEngine();
-        if (engine instanceof Compilable) {
-            /**
-             * we only care about creating an EventHandler that monitors scripts generated by script engines which implement Compilable
-             */
-            for (String extension : scriptEngineFactory.getExtensions()) {
-                extensions.add(extension);
-            }
-            if (active) {
-                configureCache();
+    @Override
+    public void handleEvent(Event event) {
+        extensions.clear();
+        for (ScriptEngineFactory factory :  slingScriptEngineManager.getEngineFactories()) {
+            ScriptEngine scriptEngine = factory.getScriptEngine();
+            if (scriptEngine instanceof Compilable) {
+                extensions.addAll(factory.getExtensions());
+                if (active) {
+                    configureCache();
+                }
             }
         }
     }
-
-    protected void unbindScriptEngineFactory(ScriptEngineFactory scriptEngineFactory) {
-        for (String extension : scriptEngineFactory.getExtensions()) {
-            extensions.remove(extension);
-        }
-        if (active) {
-            configureCache();
-        }
-    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineConsolePlugin.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineConsolePlugin.java
index 59b2658..1b88807 100644
--- a/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineConsolePlugin.java
+++ b/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineConsolePlugin.java
@@ -18,7 +18,9 @@
  */
 package org.apache.sling.scripting.core.impl;
 
+import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.Arrays;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.Iterator;
@@ -26,57 +28,59 @@ import java.util.List;
 
 import javax.script.ScriptEngineFactory;
 import javax.script.ScriptEngineManager;
-
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.webconsole.AbstractWebConsolePlugin;
+import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.ServiceRegistration;
-
-public class ScriptEngineConsolePlugin {
-
-    // --------- setup and shutdown
-
-    private static ScriptEngineConsolePlugin INSTANCE;
-
-    static void initPlugin(BundleContext context,
-            ScriptEngineManagerFactory scriptEngineManagerFactory) {
-        if (INSTANCE == null) {
-            ScriptEngineConsolePlugin tmp = new ScriptEngineConsolePlugin(
-                    scriptEngineManagerFactory);
-            tmp.activate(context);
-            INSTANCE = tmp;
-        }
-    }
-
-    static void destroyPlugin() {
-        if (INSTANCE != null) {
-            try {
-                INSTANCE.deactivate();
-            } finally {
-                INSTANCE = null;
-            }
-        }
-    }
-
-    private ServiceRegistration serviceRegistration;
-
-    private final ScriptEngineManagerFactory scriptEngineManagerFactory;
-
-    // private constructor to force using static setup and shutdown
-    private ScriptEngineConsolePlugin(
-            ScriptEngineManagerFactory scriptEngineManagerFactory) {
-        this.scriptEngineManagerFactory = scriptEngineManagerFactory;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+
+@Component(
+        property = {
+                Constants.SERVICE_DESCRIPTION + "=Web Console Plugin for ScriptEngine implementations",
+                Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
+                WebConsoleConstants.PLUGIN_LABEL + "=" + ScriptEngineConsolePlugin.CONSOLE_LABEL,
+                WebConsoleConstants.PLUGIN_TITLE + "=" + ScriptEngineConsolePlugin.CONSOLE_TITLE,
+                WebConsoleConstants.CONFIG_PRINTER_MODES + "=always",
+                WebConsoleConstants.PLUGIN_CATEGORY + "=Status"
+        },
+        service = { Servlet.class }
+)
+public class ScriptEngineConsolePlugin extends AbstractWebConsolePlugin {
+
+    public static final String CONSOLE_LABEL = "slingscripting";
+    public static final String CONSOLE_TITLE = "Script Engines";
+
+    @Reference
+    private SlingScriptEngineManager slingScriptEngineManager;
+
+    @Override
+    public String getTitle() {
+        return CONSOLE_TITLE;
     }
 
-    public String getTitle() {
-        return "Script Engines";
+    @Override
+    public String getLabel() {
+        return CONSOLE_LABEL;
     }
 
-    public void printConfiguration(final PrintWriter pw) {
+    @Override
+    protected void renderContent(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
+            throws IOException {
+        PrintWriter pw = httpServletResponse.getWriter();
+        pw.println("<div id='content' class='ui-widget'><br>");
+        pw.println("<pre>");
         pw.println("Available Script Engines");
         pw.println("========================");
 
-        ScriptEngineManager manager = scriptEngineManagerFactory.getScriptEngineManager();
-        List<?> factories = manager.getEngineFactories();
+        List<?> factories = slingScriptEngineManager.getEngineFactories();
         for (Iterator<?> fi = factories.iterator(); fi.hasNext();) {
 
             final ScriptEngineFactory factory = (ScriptEngineFactory) fi.next();
@@ -100,6 +104,8 @@ public class ScriptEngineConsolePlugin {
             pw.print("- Names : ");
             printArray(pw, factory.getNames());
         }
+        pw.println("</pre>");
+        pw.println("</div>");
     }
 
     private void printArray(PrintWriter pw, List<?> values) {
@@ -116,25 +122,4 @@ public class ScriptEngineConsolePlugin {
         }
     }
 
-    public void activate(BundleContext context) {
-        final Dictionary<String, Object> props = new Hashtable<String, Object>();
-        props.put(Constants.SERVICE_DESCRIPTION,
-            "Web Console Plugin for ScriptEngine implementations");
-        props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
-        props.put(Constants.SERVICE_PID, getClass().getName());
-
-        props.put("felix.webconsole.label", "slingscripting");
-        props.put("felix.webconsole.title", "Script Engines");
-        props.put("felix.webconsole.configprinter.modes", "always");
-
-        serviceRegistration = context.registerService(
-            this.getClass().getName(), this, props);
-    }
-
-    public void deactivate() {
-        if (serviceRegistration != null) {
-            serviceRegistration.unregister();
-            serviceRegistration = null;
-        }
-    }
 }
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactory.java b/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactory.java
deleted file mode 100644
index e851676..0000000
--- a/src/main/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactory.java
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * 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.sling.scripting.core.impl;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.util.Dictionary;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
-
-import org.apache.sling.api.scripting.SlingScriptConstants;
-import org.apache.sling.scripting.core.impl.helper.ProxyScriptEngineManager;
-import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleListener;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.component.ComponentContext;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-import org.osgi.service.component.annotations.ReferenceCardinality;
-import org.osgi.service.component.annotations.ReferencePolicy;
-import org.osgi.service.component.annotations.ReferencePolicyOption;
-import org.osgi.service.event.Event;
-import org.osgi.service.event.EventAdmin;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Component which exposes a ScriptEngineManager service.
- *
- */
-@Component(service = {})
-public class ScriptEngineManagerFactory implements BundleListener {
-
-    private final Logger log = LoggerFactory.getLogger(ScriptEngineManagerFactory.class);
-
-    private static final String ENGINE_FACTORY_SERVICE = "META-INF/services/" + ScriptEngineFactory.class.getName();
-
-    private BundleContext bundleContext;
-
-    /**
-     * Event admin is optional
-     */
-    @Reference(policy = ReferencePolicy.DYNAMIC,
-            policyOption = ReferencePolicyOption.GREEDY,
-            cardinality=ReferenceCardinality.OPTIONAL)
-    private volatile EventAdmin eventAdmin;
-
-    /**
-     * The proxy to the actual ScriptEngineManager. This proxy is actually
-     * registered as the ScriptEngineManager service for the lifetime of
-     * this factory.
-     */
-    private final ProxyScriptEngineManager scriptEngineManager = new ProxyScriptEngineManager();
-
-    private final Set<Bundle> engineSpiBundles = new HashSet<>();
-
-    private final Map<ScriptEngineFactory, Map<Object, Object>> engineSpiServices = new HashMap<>();
-
-    private ServiceRegistration scriptEngineManagerRegistration;
-
-    /**
-     * Refresh the script engine manager.
-     */
-    private void refreshScriptEngineManager() {
-        // create (empty) script engine manager
-        final ClassLoader loader = getClass().getClassLoader();
-        final SlingScriptEngineManager tmp = new SlingScriptEngineManager(loader);
-
-        // register script engines from bundles
-        synchronized (this.engineSpiBundles) {
-            for (final Bundle bundle : this.engineSpiBundles) {
-                registerFactories(tmp, bundle);
-            }
-        }
-
-        // register script engines from registered services
-        synchronized (this.engineSpiServices) {
-            for (final Map.Entry<ScriptEngineFactory, Map<Object, Object>> factory : this.engineSpiServices.entrySet()) {
-                registerFactory(tmp, factory.getKey(), factory.getValue());
-            }
-        }
-
-        scriptEngineManager.setDelegatee(tmp);
-
-        final List<ScriptEngineFactory> factories = tmp.getEngineFactories();
-        for (final ScriptEngineFactory factory : factories) {
-            log.info("ScriptEngine {}/{} is now handling {}, {}, {}.", new Object[]{factory.getEngineName(), factory.getEngineVersion(), factory.getExtensions(), factory.getMimeTypes(), factory.getNames()});
-        }
-    }
-
-    @SuppressWarnings("unchecked")
-    private void registerFactories(final SlingScriptEngineManager mgr, final Bundle bundle) {
-        URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
-        InputStream ins = null;
-        try {
-            ins = url.openStream();
-            BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
-            String line;
-            while ((line = reader.readLine()) != null) {
-                if (!line.startsWith("#") && line.trim().length() > 0) {
-	                try {
-	                    Class<ScriptEngineFactory> clazz = (Class<ScriptEngineFactory>) bundle.loadClass(line);
-	                    ScriptEngineFactory spi = clazz.newInstance();
-	                    registerFactory(mgr, spi, null);
-	                } catch (Throwable t) {
-	                    log.error("Cannot register ScriptEngineFactory " + line, t);
-	                }
-            	}
-            }
-        } catch (IOException ioe) {
-            // ignore
-        } finally {
-            if (ins != null) {
-                try {
-                    ins.close();
-                } catch (IOException ioe) {
-                }
-            }
-        }
-    }
-
-    private void registerFactory(final SlingScriptEngineManager mgr, final ScriptEngineFactory factory, final Map<Object, Object> props) {
-        log.info("Adding ScriptEngine {}/{} for language {}/{}.", new Object[]{factory.getEngineName(), factory.getEngineVersion(), factory.getLanguageName(), factory.getLanguageVersion()});
-        mgr.registerScriptEngineFactory(factory, props);
-    }
-
-    // ---------- BundleListener interface -------------------------------------
-
-    @Override
-    public void bundleChanged(BundleEvent event) {
-        if (event.getType() == BundleEvent.STARTED
-            && event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
-            synchronized (this.engineSpiBundles) {
-                this.engineSpiBundles.add(event.getBundle());
-            }
-            this.refreshScriptEngineManager();
-        } else if (event.getType() == BundleEvent.STOPPED) {
-            boolean refresh;
-            synchronized (this.engineSpiBundles) {
-                refresh = this.engineSpiBundles.remove(event.getBundle());
-            }
-            if (refresh) {
-                this.refreshScriptEngineManager();
-            }
-        }
-    }
-
-    // ---------- SCR integration ----------------------------------------------
-
-    protected void activate(ComponentContext context) {
-        this.bundleContext = context.getBundleContext();
-
-        this.bundleContext.addBundleListener(this);
-
-        Bundle[] bundles = this.bundleContext.getBundles();
-        synchronized (this.engineSpiBundles) {
-            for (Bundle bundle : bundles) {
-                if (bundle.getState() == Bundle.ACTIVE
-                    && bundle.getEntry(ENGINE_FACTORY_SERVICE) != null) {
-                    this.engineSpiBundles.add(bundle);
-                }
-            }
-        }
-
-        // create a script engine manager
-        this.refreshScriptEngineManager();
-
-        scriptEngineManagerRegistration = this.bundleContext.registerService(
-            new String[] { ScriptEngineManager.class.getName(),
-                SlingScriptEngineManager.class.getName() },
-            scriptEngineManager, new Hashtable<String, Object>());
-
-        org.apache.sling.scripting.core.impl.ScriptEngineConsolePlugin.initPlugin(context.getBundleContext(), this);
-    }
-
-    protected void deactivate(ComponentContext context) {
-        org.apache.sling.scripting.core.impl.ScriptEngineConsolePlugin.destroyPlugin();
-
-        context.getBundleContext().removeBundleListener(this);
-
-        if (scriptEngineManagerRegistration != null) {
-            scriptEngineManagerRegistration.unregister();
-            scriptEngineManagerRegistration = null;
-        }
-
-        synchronized ( this ) {
-            this.engineSpiBundles.clear();
-            this.engineSpiServices.clear();
-        }
-
-        scriptEngineManager.setDelegatee(null);
-
-        this.bundleContext = null;
-    }
-
-    @Reference(service = ScriptEngineFactory.class,
-            policy = ReferencePolicy.DYNAMIC,
-            cardinality = ReferenceCardinality.MULTIPLE)
-    protected void bindScriptEngineFactory(final ScriptEngineFactory scriptEngineFactory, final Map<Object, Object> props) {
-        if (scriptEngineFactory != null) {
-            synchronized ( this.engineSpiServices) {
-                this.engineSpiServices.put(scriptEngineFactory, props);
-            }
-
-            this.refreshScriptEngineManager();
-
-            // send event
-            postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_ADDED, scriptEngineFactory);
-        }
-    }
-
-    protected void unbindScriptEngineFactory(final ScriptEngineFactory scriptEngineFactory) {
-        boolean refresh;
-        synchronized (this.engineSpiServices) {
-            refresh = this.engineSpiServices.remove(scriptEngineFactory) != null;
-        }
-
-        if (refresh) {
-            this.refreshScriptEngineManager();
-        }
-
-        // send event
-        postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_REMOVED, scriptEngineFactory);
-    }
-
-    private String[] toArray(final List<String> list) {
-        return list.toArray(new String[list.size()]);
-    }
-
-    /**
-     * Post a notification with the EventAdmin
-     */
-    private void postEvent(final String topic, final ScriptEngineFactory scriptEngineFactory) {
-        final EventAdmin localEA = this.eventAdmin;
-        if (localEA != null) {
-            final Dictionary<String, Object> props = new Hashtable<>();
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_NAME, scriptEngineFactory.getEngineName());
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION, scriptEngineFactory.getEngineVersion());
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS, toArray(scriptEngineFactory.getExtensions()));
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME, scriptEngineFactory.getLanguageName());
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION, scriptEngineFactory.getLanguageVersion());
-            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES, toArray(scriptEngineFactory.getMimeTypes()));
-            localEA.postEvent(new Event(topic, props));
-        }
-    }
-
-    /**
-     * Get the script engine manager.
-     * Refresh the manager if changes occurred.
-     */
-    ScriptEngineManager getScriptEngineManager() {
-        return this.scriptEngineManager;
-    }
-}
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
index 04b6cb2..4b94191 100644
--- a/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
+++ b/src/main/java/org/apache/sling/scripting/core/impl/SlingScriptAdapterFactory.java
@@ -27,7 +27,7 @@ import org.apache.sling.commons.mime.MimeTypeProvider;
 import org.apache.sling.scripting.api.BindingsValuesProvider;
 import org.apache.sling.scripting.api.BindingsValuesProvidersByContext;
 import org.apache.sling.scripting.api.ScriptCache;
-import org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager;
+import org.apache.sling.scripting.core.impl.jsr223.SlingScriptEngineManager;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.ComponentContext;
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/helper/ProxyScriptEngineManager.java b/src/main/java/org/apache/sling/scripting/core/impl/helper/ProxyScriptEngineManager.java
deleted file mode 100644
index d699828..0000000
--- a/src/main/java/org/apache/sling/scripting/core/impl/helper/ProxyScriptEngineManager.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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.sling.scripting.core.impl.helper;
-
-import java.util.List;
-import java.util.Map;
-
-import javax.script.Bindings;
-import javax.script.ScriptEngine;
-import javax.script.ScriptEngineFactory;
-
-public class ProxyScriptEngineManager extends SlingScriptEngineManager {
-
-    private SlingScriptEngineManager delegatee;
-
-    public ProxyScriptEngineManager() {
-        setDelegatee(null);
-    }
-
-    public void setDelegatee(SlingScriptEngineManager delegatee) {
-        this.delegatee = (delegatee == null)
-                ? new SlingScriptEngineManager()
-                : delegatee;
-    }
-
-    public Object get(String key) {
-        return delegatee.get(key);
-    }
-
-    public ScriptEngine getEngineByExtension(String extension) {
-        return delegatee.getEngineByExtension(extension);
-    }
-
-    public ScriptEngine getEngineByMimeType(String mimeType) {
-        return delegatee.getEngineByMimeType(mimeType);
-    }
-
-    public ScriptEngine getEngineByName(String shortName) {
-        return delegatee.getEngineByName(shortName);
-    }
-
-    public List<ScriptEngineFactory> getEngineFactories() {
-        return delegatee.getEngineFactories();
-    }
-
-    public Map<Object, Object> getProperties(ScriptEngineFactory factory) {
-        return delegatee.getProperties(factory);
-    }
-
-    public void put(String key, Object value) {
-        delegatee.put(key, value);
-    }
-
-    public void registerEngineExtension(String extension,
-            ScriptEngineFactory factory) {
-        delegatee.registerEngineExtension(extension, factory);
-    }
-
-    public void registerEngineMimeType(String type, ScriptEngineFactory factory) {
-        delegatee.registerEngineMimeType(type, factory);
-    }
-
-    public void registerEngineName(String name, ScriptEngineFactory factory) {
-        delegatee.registerEngineName(name, factory);
-    }
-
-    public void registerScriptEngineFactory(ScriptEngineFactory factory,
-            Map<Object, Object> props) {
-        delegatee.registerScriptEngineFactory(factory, props);
-    }
-
-    // Livetribe JSR-223 2.0.6 API
-    public Bindings getGlobalScope()
-    {
-        return getBindings();
-    }
-
-    // Livetribe JSR-223 2.0.6 API
-    public void setGlobalScope(Bindings globalScope)
-    {
-        setBindings(globalScope);
-    }
-
-    public Bindings getBindings() {
-        return delegatee.getBindings();
-    }
-
-    public void setBindings(Bindings bindings) {
-        delegatee.setBindings(bindings);
-    }
-}
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/helper/SlingScriptEngineManager.java b/src/main/java/org/apache/sling/scripting/core/impl/helper/SlingScriptEngineManager.java
deleted file mode 100644
index 32f727e..0000000
--- a/src/main/java/org/apache/sling/scripting/core/impl/helper/SlingScriptEngineManager.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * 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.sling.scripting.core.impl.helper;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.script.ScriptEngineFactory;
-import javax.script.ScriptEngineManager;
-
-
-public class SlingScriptEngineManager extends ScriptEngineManager {
-
-    private final List<ScriptEngineFactory> factories = new ArrayList<ScriptEngineFactory>();
-    private final Map<ScriptEngineFactory, Map<Object, Object>> factoryProperties = new HashMap<ScriptEngineFactory, Map<Object, Object>>();
-
-    public SlingScriptEngineManager(ClassLoader classLoader) {
-        super(classLoader);
-    }
-
-    public SlingScriptEngineManager() {
-        super();
-    }
-
-    @Override
-    public List<ScriptEngineFactory> getEngineFactories() {
-        List<ScriptEngineFactory> baseFactories = super.getEngineFactories();
-
-        List<ScriptEngineFactory> result = new ArrayList<ScriptEngineFactory>();
-        result.addAll(factories);
-        result.addAll(baseFactories);
-        return result;
-    }
-
-    public Map<Object, Object> getProperties(ScriptEngineFactory factory) {
-        return factoryProperties.get(factory);
-    }
-
-    public void registerScriptEngineFactory(ScriptEngineFactory factory, Map<Object, Object> props) {
-        for (Object ext : factory.getExtensions()) {
-            registerEngineExtension((String) ext, factory);
-        }
-
-        for (Object mime : factory.getMimeTypes()) {
-            registerEngineMimeType((String) mime, factory);
-        }
-
-        for (Object name : factory.getNames()) {
-            registerEngineName((String) name, factory);
-        }
-
-        factories.add(factory);
-
-        if (props != null) {
-            factoryProperties.put(factory, props);
-        }
-    }
-
-}
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager.java b/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager.java
new file mode 100644
index 0000000..8c52960
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager.java
@@ -0,0 +1,320 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scripting.core.impl.jsr223;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+
+import org.apache.sling.api.scripting.SlingScriptConstants;
+import org.apache.sling.commons.osgi.PropertiesUtil;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.osgi.service.component.annotations.ReferencePolicy;
+import org.osgi.service.component.annotations.ReferencePolicyOption;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventAdmin;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Component(
+        service = {ScriptEngineManager.class, SlingScriptEngineManager.class},
+        reference = @Reference(
+                name = "ScriptEngineFactory",
+                bind = "bindScriptEngineFactory",
+                unbind = "unbindScriptEngineFactory",
+                service = ScriptEngineFactory.class,
+                cardinality = ReferenceCardinality.MULTIPLE,
+                policy = ReferencePolicy.DYNAMIC
+        )
+)
+public class SlingScriptEngineManager extends ScriptEngineManager implements BundleListener {
+
+    public static final String EVENT_TOPIC_SCRIPT_MANAGER_UPDATED =
+            "org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/UPDATED";
+    public static final String ENGINE_FACTORY_SERVICE = "META-INF/services/" + ScriptEngineFactory.class.getName();
+
+    private static final Logger LOG = LoggerFactory.getLogger(SlingScriptEngineManager.class);
+
+    private final Set<Bundle> engineSpiBundles = new HashSet<>();
+    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+    private final Map<ScriptEngineFactory, Map<String, Object>> factoriesProperties = new HashMap<>();
+    private final Set<ServiceReference<ScriptEngineFactory>> serviceReferences = new HashSet<>();
+
+    private ScriptEngineManager internalManager = new ScriptEngineManager(SlingScriptEngineManager.class.getClassLoader());
+    private SortedSet<SortableScriptEngineFactory> factories = new TreeSet<>();
+    private ComponentContext componentContext;
+
+    @Reference(policy = ReferencePolicy.DYNAMIC,
+               policyOption = ReferencePolicyOption.GREEDY,
+               cardinality = ReferenceCardinality.OPTIONAL)
+    private volatile EventAdmin eventAdmin;
+
+    @Override
+    public ScriptEngine getEngineByName(String shortName) {
+        readWriteLock.readLock().lock();
+        try {
+            return internalManager.getEngineByName(shortName);
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public ScriptEngine getEngineByExtension(String extension) {
+        readWriteLock.readLock().lock();
+        try {
+            return internalManager.getEngineByExtension(extension);
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public ScriptEngine getEngineByMimeType(String mimeType) {
+        readWriteLock.readLock().lock();
+        try {
+            return internalManager.getEngineByMimeType(mimeType);
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public List<ScriptEngineFactory> getEngineFactories() {
+        readWriteLock.readLock().lock();
+        try {
+            ArrayList<ScriptEngineFactory> list = new ArrayList<>(factories.size());
+            list.addAll(factories);
+            return Collections.unmodifiableList(list);
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    @Override
+    public void registerEngineName(String name, ScriptEngineFactory factory) {
+        readWriteLock.writeLock().lock();
+        try {
+            internalManager.registerEngineName(name, factory);
+        } finally {
+            readWriteLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public void registerEngineMimeType(String type, ScriptEngineFactory factory) {
+        readWriteLock.writeLock().lock();
+        try {
+            internalManager.registerEngineMimeType(type, factory);
+        } finally {
+            readWriteLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public void registerEngineExtension(String extension, ScriptEngineFactory factory) {
+        readWriteLock.writeLock().lock();
+        try {
+            internalManager.registerEngineExtension(extension, factory);
+        } finally {
+            readWriteLock.writeLock().unlock();
+        }
+    }
+
+    @Override
+    public void bundleChanged(BundleEvent event) {
+        if (event.getType() == BundleEvent.STARTED
+                && event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
+            synchronized (this.engineSpiBundles) {
+                this.engineSpiBundles.add(event.getBundle());
+            }
+            updateFactories();
+        } else if (event.getType() == BundleEvent.STOPPED) {
+            boolean refresh;
+            synchronized (this.engineSpiBundles) {
+                refresh = this.engineSpiBundles.remove(event.getBundle());
+            }
+            if (refresh) {
+                updateFactories();
+            }
+        }
+    }
+
+    public Map<String, Object> getProperties(ScriptEngineFactory factory) {
+        readWriteLock.readLock().lock();
+        try {
+            return factoriesProperties.get(factory);
+        } finally {
+            readWriteLock.readLock().unlock();
+        }
+    }
+
+    @Activate
+    private void activate(ComponentContext componentContext) {
+        this.componentContext = componentContext;
+        updateFactories();
+    }
+
+    private void bindScriptEngineFactory(final ServiceReference<ScriptEngineFactory> serviceReference,
+                                           final ScriptEngineFactory factory) {
+        synchronized (this.serviceReferences) {
+            serviceReferences.add(serviceReference);
+        }
+        updateFactories();
+        postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_ADDED, factory);
+    }
+
+    private void unbindScriptEngineFactory(final ServiceReference<ScriptEngineFactory> serviceReference, final ScriptEngineFactory
+            factory) {
+        synchronized (this.serviceReferences) {
+            serviceReferences.remove(serviceReference);
+            if (componentContext != null) {
+                componentContext.getBundleContext().ungetService(serviceReference);
+            }
+        }
+        updateFactories();
+        postEvent(SlingScriptConstants.TOPIC_SCRIPT_ENGINE_FACTORY_REMOVED, factory);
+    }
+
+    private void updateFactories() {
+        readWriteLock.writeLock().lock();
+        try {
+            factories = new TreeSet<>();
+            internalManager = new ScriptEngineManager(SlingScriptEngineManager.class.getClassLoader());
+            long fakeBundleIdCounter = Long.MIN_VALUE;
+            // first add the platform factories
+            for (ScriptEngineFactory factory : internalManager.getEngineFactories()) {
+                SortableScriptEngineFactory ssef = new SortableScriptEngineFactory(factory, fakeBundleIdCounter++, 0);
+                factories.add(ssef);
+            }
+            // then factories from SPI Bundles
+            for (Bundle bundle : engineSpiBundles) {
+                URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
+                InputStream ins = null;
+                try {
+                    ins = url.openStream();
+                    BufferedReader reader = new BufferedReader(new InputStreamReader(ins, StandardCharsets.UTF_8));
+                    String line;
+                    while ((line = reader.readLine()) != null) {
+                        if (!line.startsWith("#") && line.trim().length() > 0) {
+                            try {
+                                Class<ScriptEngineFactory> clazz = (Class<ScriptEngineFactory>) bundle.loadClass(line);
+                                SortableScriptEngineFactory ssef = new SortableScriptEngineFactory(clazz.getDeclaredConstructor()
+                                        .newInstance(), fakeBundleIdCounter++, 0);
+                                factories.add(ssef);
+                            } catch (Throwable t) {
+                                LOG.error("Cannot register ScriptEngineFactory " + line, t);
+                            }
+                        }
+                    }
+                } catch (IOException ioe) {
+                    LOG.error("Unable to process bundle " + bundle.getSymbolicName(), ioe);
+                } finally {
+                    if (ins != null) {
+                        try {
+                            ins.close();
+                        } catch (IOException ioe) {
+                            LOG.error("Unable to release stream resource.", ioe);
+                        }
+                    }
+                }
+            }
+            // and finally factories registered as OSGi services
+            if (componentContext != null) {
+                factoriesProperties.clear();
+                for (ServiceReference<ScriptEngineFactory> serviceReference : serviceReferences) {
+                    ScriptEngineFactory scriptEngineFactory = componentContext.getBundleContext().getService(serviceReference);
+                    SortableScriptEngineFactory sortableScriptEngineFactory =
+                            new SortableScriptEngineFactory(scriptEngineFactory, serviceReference.getBundle().getBundleId(),
+                                    PropertiesUtil.toInteger(serviceReference.getProperty(Constants.SERVICE_RANKING), 0));
+                    factories.add(sortableScriptEngineFactory);
+                    Map<String, Object> factoryProperties = new HashMap<>(serviceReference.getPropertyKeys().length);
+                    for (String key : serviceReference.getPropertyKeys()) {
+                        factoryProperties.put(key, serviceReference.getProperty(key));
+                    }
+                    factoriesProperties.put(scriptEngineFactory, factoryProperties);
+                }
+            }
+            // register the associations at the end, so that the priority sorting is taken into consideration
+            for (ScriptEngineFactory factory : factories) {
+                registerAssociations(factory);
+            }
+            if (eventAdmin != null) {
+                eventAdmin.postEvent(new Event(EVENT_TOPIC_SCRIPT_MANAGER_UPDATED, Collections.EMPTY_MAP));
+            }
+        } finally {
+            readWriteLock.writeLock().unlock();
+        }
+    }
+
+    private void registerAssociations(ScriptEngineFactory factory) {
+        for (String extension : factory.getExtensions()) {
+            internalManager.registerEngineExtension(extension, factory);
+        }
+        for (String mimeType : factory.getMimeTypes()) {
+            internalManager.registerEngineMimeType(mimeType, factory);
+        }
+        for (String name : factory.getNames()) {
+            internalManager.registerEngineName(name, factory);
+        }
+    }
+
+    private void postEvent(final String topic, final ScriptEngineFactory scriptEngineFactory) {
+        if (eventAdmin != null) {
+            final Dictionary<String, Object> props = new Hashtable<>();
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_NAME, scriptEngineFactory.getEngineName());
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_VERSION, scriptEngineFactory.getEngineVersion());
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_EXTENSIONS, scriptEngineFactory.getExtensions().toArray(new
+                    String[0]));
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_NAME, scriptEngineFactory.getLanguageName());
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_LANGUAGE_VERSION, scriptEngineFactory.getLanguageVersion());
+            props.put(SlingScriptConstants.PROPERTY_SCRIPT_ENGINE_FACTORY_MIME_TYPES, scriptEngineFactory.getMimeTypes().toArray(new
+                    String[0]));
+            eventAdmin.postEvent(new Event(topic, props));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactory.java b/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactory.java
new file mode 100644
index 0000000..b04573f
--- /dev/null
+++ b/src/main/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactory.java
@@ -0,0 +1,134 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scripting.core.impl.jsr223;
+
+import java.util.List;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+public class SortableScriptEngineFactory implements ScriptEngineFactory, Comparable {
+
+    private final ScriptEngineFactory delegate;
+    private final int serviceRanking;
+    private final long bundleId;
+
+    /**
+     * Constructor for implicit {@link ScriptEngineFactory} provided by the platform.
+     *
+     * @param delegate the actual {@link ScriptEngineFactory}
+     */
+    SortableScriptEngineFactory(ScriptEngineFactory delegate) {
+        this.delegate = delegate;
+        serviceRanking = 0;
+        bundleId = 0;
+    }
+
+    /**
+     * Constructor for OSGi registered {@link ScriptEngineFactory} instances.
+     *
+     * @param delegate       the actual {@link ScriptEngineFactory}
+     * @param bundleId       the bundle id of the bundle registering the {@link ScriptEngineFactory}
+     * @param serviceRanking the service ranking of the {@link ScriptEngineFactory}
+     */
+    SortableScriptEngineFactory(ScriptEngineFactory delegate, long bundleId, int serviceRanking) {
+        this.delegate = delegate;
+        this.bundleId = bundleId;
+        this.serviceRanking = serviceRanking;
+    }
+
+    @Override
+    public String getEngineName() {
+        return delegate.getEngineName();
+    }
+
+    @Override
+    public String getEngineVersion() {
+        return delegate.getEngineVersion();
+    }
+
+    @Override
+    public List<String> getExtensions() {
+        return delegate.getExtensions();
+    }
+
+    @Override
+    public List<String> getMimeTypes() {
+        return delegate.getMimeTypes();
+    }
+
+    @Override
+    public List<String> getNames() {
+        return delegate.getNames();
+    }
+
+    @Override
+    public String getLanguageName() {
+        return delegate.getLanguageName();
+    }
+
+    @Override
+    public String getLanguageVersion() {
+        return delegate.getLanguageVersion();
+    }
+
+    @Override
+    public Object getParameter(String key) {
+        return delegate.getParameter(key);
+    }
+
+    @Override
+    public String getMethodCallSyntax(String obj, String m, String... args) {
+        return delegate.getMethodCallSyntax(obj, m, args);
+    }
+
+    @Override
+    public String getOutputStatement(String toDisplay) {
+        return delegate.getOutputStatement(toDisplay);
+    }
+
+    @Override
+    public String getProgram(String... statements) {
+        return delegate.getProgram(statements);
+    }
+
+    @Override
+    public ScriptEngine getScriptEngine() {
+        return delegate.getScriptEngine();
+    }
+
+    @Override
+    public int compareTo(Object o) {
+        SortableScriptEngineFactory other = (SortableScriptEngineFactory) o;
+        if (equals(other)) {
+            return 0;
+        }
+        if (serviceRanking == other.serviceRanking) {
+            if (bundleId > other.bundleId) {
+                return 1;
+            } else if (bundleId == other.bundleId) {
+                return 0;
+            }
+            return -1;
+        } else if (serviceRanking > other.serviceRanking) {
+            return 1;
+        }
+        return -1;
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/core/ScriptHelperTest.java b/src/test/java/org/apache/sling/scripting/core/ScriptHelperTest.java
index af578ec..646a3eb 100644
--- a/src/test/java/org/apache/sling/scripting/core/ScriptHelperTest.java
+++ b/src/test/java/org/apache/sling/scripting/core/ScriptHelperTest.java
@@ -26,7 +26,7 @@ import static org.junit.Assert.fail;
 import java.util.Dictionary;
 import java.util.Hashtable;
 
-import org.apache.sling.testing.mock.sling.junit.SlingContext;
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -35,7 +35,7 @@ import org.osgi.framework.Constants;
 public class ScriptHelperTest {
     
     @Rule
-    public SlingContext sling = new SlingContext();
+    public OsgiContext sling = new OsgiContext();
     
     private ScriptHelper sh;
     private final int [] RANKINGS = { 42, 62, -12, 76, -123, 0, 7432, -21 };
@@ -74,4 +74,4 @@ public class ScriptHelperTest {
         assertEquals(RANKINGS.length, svc.length);
         assertHigherRankingComesFirst(svc);
     }
-}
\ No newline at end of file
+}
diff --git a/src/test/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactoryTest.java b/src/test/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactoryTest.java
deleted file mode 100644
index de8c85d..0000000
--- a/src/test/java/org/apache/sling/scripting/core/impl/ScriptEngineManagerFactoryTest.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * 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.sling.scripting.core.impl;
-
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.Dictionary;
-
-import javax.script.ScriptEngineManager;
-
-import org.jmock.Expectations;
-import org.jmock.Mockery;
-import org.jmock.integration.junit4.JMock;
-import org.jmock.integration.junit4.JUnit4Mockery;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
-import org.osgi.framework.BundleListener;
-import org.osgi.framework.ServiceListener;
-import org.osgi.framework.ServiceReference;
-import org.osgi.framework.ServiceRegistration;
-import org.osgi.service.component.ComponentContext;
-
-/**
- * Test of the ScriptEngineManagerFactory.
- */
-@RunWith(JMock.class)
-public class ScriptEngineManagerFactoryTest {
-
-    private static Class<?> SCRIPT_ENGINE_FACTORY = DummyScriptEngineFactory.class;
-
-    private Mockery context = new JUnit4Mockery();
-
-    private ComponentContext componentCtx;
-
-    private BundleContext bundleCtx;
-
-    @Before
-    public void setup() throws Exception {
-        componentCtx = context.mock(ComponentContext.class);
-        bundleCtx = context.mock(BundleContext.class);
-        context.checking(new Expectations(){{
-            atLeast(1).of(componentCtx).getBundleContext();
-            will(returnValue(bundleCtx));
-
-            allowing(bundleCtx).createFilter(with(any(String.class)));
-            allowing(bundleCtx).addServiceListener(with(any(ServiceListener.class)));
-            allowing(bundleCtx).addServiceListener(with(any(ServiceListener.class)), with(any(String.class)));
-            allowing(bundleCtx).getServiceReferences(with(any(String.class)), with(aNull(String.class)));
-            allowing(bundleCtx).getServiceReferences(with(aNull(String.class)), with(aNull(String.class)));
-            allowing(bundleCtx).getServiceReferences(with(any(String.class)), with(any(String.class)));
-            allowing(bundleCtx).getServiceReferences(with(aNull(String.class)), with(any(String.class)));
-
-            one(bundleCtx).addBundleListener(with(any(BundleListener.class)));
-            one(bundleCtx).getBundles();
-            will(returnValue(new Bundle[0]));
-
-            allowing(bundleCtx).registerService(with(equal("org.apache.sling.scripting.core.impl.ScriptEngineConsolePlugin")), with(any(Object.class)), with(any(Dictionary.class)));
-            will(returnValue(new MockServiceRegistration()));
-
-
-        }});
-    }
-
-
-    @Test
-    public void checkNonNullManagerAfterActivate() throws Exception  {
-        context.checking(new Expectations(){{
-            one(bundleCtx).registerService(with(equal(new String[] {"javax.script.ScriptEngineManager", "org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager"})), with(any(Object.class)), with(any(Dictionary.class)));
-            will(returnValue(new MockServiceRegistration()));
-        }});
-
-        ScriptEngineManagerFactory factory = new ScriptEngineManagerFactory();
-        factory.activate(componentCtx);
-
-        assertNotNull(factory.getScriptEngineManager());
-    }
-
-    @Test
-    public void checkAddingScriptBundle() throws Exception {
-        context.checking(new Expectations(){{
-            exactly(1).of(bundleCtx).registerService(with(equal(new String[] {"javax.script.ScriptEngineManager", "org.apache.sling.scripting.core.impl.helper.SlingScriptEngineManager"})), with(any(Object.class)), with(any(Dictionary.class)));
-            will(returnValue(new MockServiceRegistration()));
-        }});
-
-        ScriptEngineManagerFactory factory = new ScriptEngineManagerFactory();
-        factory.activate(componentCtx);
-
-        ScriptEngineManager first = factory.getScriptEngineManager();
-
-        assertNull(first.getEngineByName("dummy"));
-
-        final Bundle bundle  = context.mock(Bundle.class);
-
-        final File factoryFile = createFactoryFile();
-
-        context.checking(new Expectations() {{
-
-            atLeast(1).of(bundle).getEntry("META-INF/services/javax.script.ScriptEngineFactory");
-            will(returnValue(factoryFile.toURI().toURL()));
-
-            atLeast(1).of(bundle).loadClass(SCRIPT_ENGINE_FACTORY.getName());
-            will(returnValue(SCRIPT_ENGINE_FACTORY));
-        }});
-
-        factory.bundleChanged(new BundleEvent(BundleEvent.STARTED, bundle));
-
-        ScriptEngineManager second = factory.getScriptEngineManager();
-
-        assertNotNull(second.getEngineByName("dummy"));
-    }
-
-    private File createFactoryFile() throws IOException {
-        File tempFile = File.createTempFile("scriptEngine", "tmp");
-        tempFile.deleteOnExit();
-
-        FileOutputStream fos = null;
-        try {
-            fos = new FileOutputStream(tempFile);
-            fos.write("#I'am a test-comment\n".getBytes());
-            fos.write(SCRIPT_ENGINE_FACTORY.getName().getBytes());
-        } finally {
-            if (fos != null) {
-                fos.close();
-            }
-        }
-        return tempFile;
-    }
-
-    private class MockServiceRegistration implements ServiceRegistration {
-
-        public ServiceReference getReference() {
-            throw new UnsupportedOperationException();
-        }
-
-        @SuppressWarnings("unchecked")
-        public void setProperties(Dictionary properties) {
-            // NO-OP
-        }
-
-        public void unregister() {
-            // NO-OP
-        }
-
-    }
-
-}
diff --git a/src/test/java/org/apache/sling/scripting/core/impl/DummyScriptEngineFactory.java b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/DummyScriptEngineFactory.java
similarity index 76%
rename from src/test/java/org/apache/sling/scripting/core/impl/DummyScriptEngineFactory.java
rename to src/test/java/org/apache/sling/scripting/core/impl/jsr223/DummyScriptEngineFactory.java
index 3c67591..9a36ecb 100644
--- a/src/test/java/org/apache/sling/scripting/core/impl/DummyScriptEngineFactory.java
+++ b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/DummyScriptEngineFactory.java
@@ -1,20 +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.sling.scripting.core.impl;
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scripting.core.impl.jsr223;
 
 import java.io.Reader;
 import java.util.Arrays;
diff --git a/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManagerTest.java b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManagerTest.java
new file mode 100644
index 0000000..a6df3c4
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManagerTest.java
@@ -0,0 +1,208 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scripting.core.impl.jsr223;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+import javax.script.ScriptEngineManager;
+
+import org.apache.sling.testing.mock.osgi.junit.OsgiContext;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventConstants;
+import org.osgi.service.event.EventHandler;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SlingScriptEngineManagerTest {
+
+    private static Class<?> SCRIPT_ENGINE_FACTORY = DummyScriptEngineFactory.class;
+
+    @Rule
+    public final OsgiContext context = new OsgiContext();
+
+
+    @Before
+    public void setUp() {
+        context.registerInjectActivateService(new SlingScriptEngineManager());
+    }
+
+    @Test
+    public void testPlatformScriptEngines() {
+        ScriptEngineManager scriptEngineManager = context.getService(ScriptEngineManager.class);
+        assertNotNull("Expected a ScriptEngineManager would be already registered.", scriptEngineManager);
+        assertEquals("The ScriptEngineManager should have had 1 ScriptEngineFactory registered.", 1, scriptEngineManager
+                .getEngineFactories().size());
+    }
+
+    @Test
+    public void testOSGiRegisteredFactoriesDifferentServiceRanking() throws Exception {
+        int expectedEvents = 3;
+        CountDownLatch latch = new CountDownLatch(expectedEvents);
+        TestEventHandler eventHandler = new TestEventHandler
+                (latch, "org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/UPDATED");
+        context.registerService(
+                EventHandler.class, eventHandler,
+                new HashMap<String, Object>() {{
+                    put(EventConstants.EVENT_TOPIC, "org/apache/sling/scripting/core/impl/jsr223/SlingScriptEngineManager/*");
+                }}
+        );
+
+        ScriptEngineFactory f1 = mockScriptEngineFactory("f1", "1.0", Collections.singletonList("f1"), "f1", "1.0", Collections
+                .singletonList("text"));
+        ScriptEngineFactory f2 = mockScriptEngineFactory("f2", "1.0", Collections.singletonList("f2"), "f2", "1.0", Collections
+                .singletonList("text"));
+
+        ServiceRegistration<ScriptEngineFactory> f1SR = context.bundleContext().registerService(ScriptEngineFactory.class, f1, new
+                Hashtable<String, Object>() {{
+                    put(Constants.SERVICE_RANKING, 2);
+                }});
+        context.bundleContext().registerService(ScriptEngineFactory.class, f2, new
+                Hashtable<String, Object>() {{
+                    put(Constants.SERVICE_RANKING, 1);
+                }});
+
+        ScriptEngineManager scriptEngineManager = context.getService(ScriptEngineManager.class);
+        assertNotNull("Expected a ScriptEngineManager would be already registered.", scriptEngineManager);
+        List<ScriptEngineFactory> factories = scriptEngineManager.getEngineFactories();
+        assertEquals("The ScriptEngineManager should have had 3 ScriptEngineFactories registered.", 3, factories.size());
+        assertEquals(f1.getEngineName(), factories.get(2).getEngineName());
+        assertEquals(f2.getEngineName(), factories.get(1).getEngineName());
+
+        SlingScriptEngineManager slingScriptEngineManager = context.getService(SlingScriptEngineManager.class);
+        assertEquals(2, slingScriptEngineManager.getProperties(f1).get(Constants.SERVICE_RANKING));
+
+        f1SR.unregister();
+
+        factories = scriptEngineManager.getEngineFactories();
+        assertEquals("The ScriptEngineManager should have had 2 ScriptEngineFactories registered.", 2, factories.size());
+        assertEquals(f2.getEngineName(), factories.get(1).getEngineName());
+
+        assertEquals(f2, scriptEngineManager.getEngineByName("f2").getFactory());
+        assertEquals(f2, scriptEngineManager.getEngineByExtension("f2").getFactory());
+        assertEquals(f2, scriptEngineManager.getEngineByMimeType("text").getFactory());
+
+        latch.await(2, TimeUnit.SECONDS);
+        assertEquals("Expected a different number of processed " + SlingScriptEngineManager.EVENT_TOPIC_SCRIPT_MANAGER_UPDATED + " events.",
+                expectedEvents, eventHandler.processedEvents);
+    }
+
+    @Test
+    public void testBundledScriptEngineFactory() throws Exception {
+        URL url = createFactoryFile().toURI().toURL();
+        Bundle bundle = mock(Bundle.class);
+        when(bundle.getEntry(SlingScriptEngineManager.ENGINE_FACTORY_SERVICE)).thenReturn(url);
+        when(bundle.loadClass(SCRIPT_ENGINE_FACTORY.getName())).thenAnswer(new Answer<Class>() {
+            @Override
+            public Class answer(InvocationOnMock invocation) {
+                return SCRIPT_ENGINE_FACTORY;
+            }
+        });
+
+        BundleEvent bundleEvent = new BundleEvent(BundleEvent.STARTED, bundle);
+        SlingScriptEngineManager slingScriptEngineManager = context.getService(SlingScriptEngineManager.class);
+        assertNotNull("Expected that the SlingScriptEngineManager would already be registered.", slingScriptEngineManager);
+        slingScriptEngineManager.bundleChanged(bundleEvent);
+        List<ScriptEngineFactory> factories = slingScriptEngineManager.getEngineFactories();
+        assertEquals("Expected 2 ScriptEngineFactories.", 2, factories.size());
+        assertEquals("Dummy Scripting Engine", factories.get(1).getEngineName());
+
+        bundleEvent = new BundleEvent(BundleEvent.STOPPED, bundle);
+        slingScriptEngineManager.bundleChanged(bundleEvent);
+        factories = slingScriptEngineManager.getEngineFactories();
+        assertEquals("Expected 1 ScriptEngineFactory.", 1, factories.size());
+        assertEquals("Oracle Nashorn", factories.get(0).getEngineName());
+    }
+
+    private ScriptEngineFactory mockScriptEngineFactory(String engineName, String engineVersion, List<String> extensions, String
+            languageName, String languageVersion, List<String> mimeTypes) {
+        ScriptEngineFactory factory = mock(ScriptEngineFactory.class);
+        when(factory.getEngineName()).thenReturn(engineName);
+        when(factory.getNames()).thenReturn(Collections.singletonList(engineName));
+        when(factory.getEngineVersion()).thenReturn(engineVersion);
+        when(factory.getExtensions()).thenReturn(extensions);
+        when(factory.getLanguageName()).thenReturn(languageName);
+        when(factory.getLanguageVersion()).thenReturn(languageVersion);
+        when(factory.getMimeTypes()).thenReturn(mimeTypes);
+        ScriptEngine scriptEngine = mock(ScriptEngine.class);
+        when(factory.getScriptEngine()).thenReturn(scriptEngine);
+        when(scriptEngine.getFactory()).thenReturn(factory);
+        return factory;
+    }
+
+    private File createFactoryFile() throws IOException {
+        File tempFile = File.createTempFile("scriptEngine", "tmp");
+        tempFile.deleteOnExit();
+
+        FileOutputStream fos = null;
+        try {
+            fos = new FileOutputStream(tempFile);
+            fos.write("#I'm a test-comment\n".getBytes());
+            fos.write(SCRIPT_ENGINE_FACTORY.getName().getBytes());
+        } finally {
+            if (fos != null) {
+                fos.close();
+            }
+        }
+        return tempFile;
+    }
+
+
+    private static class TestEventHandler implements EventHandler {
+
+        String topic;
+        CountDownLatch latch;
+        int processedEvents = 0;
+
+        public TestEventHandler(CountDownLatch latch, String topic) {
+            this.topic = topic;
+            this.latch = latch;
+        }
+
+        @Override
+        public void handleEvent(Event event) {
+            if (event.getTopic().equals(topic)) {
+                processedEvents++;
+                latch.countDown();
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactoryTest.java b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactoryTest.java
new file mode 100644
index 0000000..e220bcf
--- /dev/null
+++ b/src/test/java/org/apache/sling/scripting/core/impl/jsr223/SortableScriptEngineFactoryTest.java
@@ -0,0 +1,174 @@
+/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ~ 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.sling.scripting.core.impl.jsr223;
+
+import java.util.Collections;
+import java.util.List;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SortableScriptEngineFactoryTest {
+
+    @Test
+    public void testDelegateConstructor() {
+        ScriptEngineFactory delegate = mock(ScriptEngineFactory.class);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(delegate);
+        assertEquals(0, sortableScriptEngineFactory.compareTo(getCompareFactory(0, 0)));
+    }
+
+    @Test
+    public void getEngineName() {
+        String answer = "answer";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getEngineName()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getEngineName());
+    }
+
+    @Test
+    public void getEngineVersion() {
+        String answer = "answer";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getEngineVersion()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getEngineVersion());
+    }
+
+    @Test
+    public void getExtensions() {
+        List<String> answer = Collections.emptyList();
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getExtensions()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getExtensions());
+    }
+
+    @Test
+    public void getMimeTypes() {
+        List<String> answer = Collections.emptyList();
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getMimeTypes()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getMimeTypes());
+    }
+
+    @Test
+    public void getNames() {
+        List<String> answer = Collections.emptyList();
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getNames()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getNames());
+    }
+
+    @Test
+    public void getLanguageName() {
+        String answer = "answer";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getLanguageName()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getLanguageName());
+    }
+
+    @Test
+    public void getLanguageVersion() {
+        String answer = "answer";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getLanguageVersion()).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getLanguageVersion());
+    }
+
+    @Test
+    public void getParameter() {
+        String answer = "answer";
+        String key = "key";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getParameter(key)).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getParameter(key));
+    }
+
+    @Test
+    public void getMethodCallSyntax() {
+        String answer = "answer";
+        String obj = "obj";
+        String m = "m";
+        String[] args = new String[0];
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getMethodCallSyntax(obj, m, args)).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getMethodCallSyntax(obj, m, args));
+    }
+
+    @Test
+    public void getOutputStatement() {
+        String answer = "answer";
+        String toDisplay = "toDisplay";
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getOutputStatement(toDisplay)).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getOutputStatement(toDisplay));
+    }
+
+    @Test
+    public void getProgram() {
+        String answer = "answer";
+        String[] statements = new String[0];
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getProgram(statements)).thenReturn(answer);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(answer, sortableScriptEngineFactory.getProgram(statements));
+    }
+
+    @Test
+    public void getScriptEngine() {
+        ScriptEngine scriptEngine = mock(ScriptEngine.class);
+        ScriptEngineFactory scriptEngineFactory = mock(ScriptEngineFactory.class);
+        when(scriptEngineFactory.getScriptEngine()).thenReturn(scriptEngine);
+        SortableScriptEngineFactory sortableScriptEngineFactory = new SortableScriptEngineFactory(scriptEngineFactory, 0, 0);
+        assertEquals(scriptEngine, sortableScriptEngineFactory.getScriptEngine());
+    }
+
+    @Test
+    public void compareTo() {
+        SortableScriptEngineFactory same = getCompareFactory(1, 2);
+        assertEquals(0, same.compareTo(same));
+        assertEquals(0, getCompareFactory(0, 0).compareTo(getCompareFactory(0, 0)));
+        assertEquals(1, getCompareFactory(1, 0).compareTo(getCompareFactory(0, 0)));
+        assertEquals(1, getCompareFactory(0, 1).compareTo(getCompareFactory(0, 0)));
+        assertEquals(1, getCompareFactory(1, 1).compareTo(getCompareFactory(1, 0)));
+        assertEquals(1, getCompareFactory(1, 1).compareTo(getCompareFactory(0, 1)));
+        assertEquals(-1, getCompareFactory(0, 0).compareTo(getCompareFactory(1, 0)));
+        assertEquals(-1, getCompareFactory(0, 0).compareTo(getCompareFactory(0, 1)));
+        assertEquals(-1, getCompareFactory(0, 1).compareTo(getCompareFactory(1, 1)));
+        assertEquals(-1, getCompareFactory(0, 0).compareTo(getCompareFactory(0, 1)));
+    }
+
+    private SortableScriptEngineFactory getCompareFactory(long bundleId, int serviceRanking) {
+        return new SortableScriptEngineFactory(null, bundleId, serviceRanking);
+    }
+}
diff --git a/src/test/java/org/apache/sling/scripting/core/it/ScriptingCoreTestSupport.java b/src/test/java/org/apache/sling/scripting/core/it/ScriptingCoreTestSupport.java
index a5b90da..7f35c79 100644
--- a/src/test/java/org/apache/sling/scripting/core/it/ScriptingCoreTestSupport.java
+++ b/src/test/java/org/apache/sling/scripting/core/it/ScriptingCoreTestSupport.java
@@ -18,6 +18,8 @@
  */
 package org.apache.sling.scripting.core.it;
 
+import java.util.Arrays;
+
 import org.apache.sling.testing.paxexam.TestSupport;
 import org.ops4j.pax.exam.Configuration;
 import org.ops4j.pax.exam.Option;
@@ -28,6 +30,7 @@ import static org.apache.sling.testing.paxexam.SlingOptions.webconsole;
 import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
 import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
+import static org.ops4j.pax.exam.CoreOptions.vmOption;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
 
@@ -35,7 +38,7 @@ public class ScriptingCoreTestSupport extends TestSupport {
 
     @Configuration
     public Option[] configuration() {
-        return new Option[]{
+        Option[] configuration = new Option[]{
             baseConfiguration(),
             launchpad(),
             // Sling Scripting Core
@@ -52,6 +55,17 @@ public class ScriptingCoreTestSupport extends TestSupport {
             // testing
             junitBundles()
         };
+        try {
+            Integer javaVersion = Integer.parseInt(System.getProperty("java.specification.version"));
+            if (javaVersion >= 9) {
+                Option[] java9AndBeyondConfiguration = Arrays.copyOf(configuration, configuration.length + 1);
+                java9AndBeyondConfiguration[java9AndBeyondConfiguration.length - 1] = vmOption("--add-modules=java.se.ee");
+                configuration = java9AndBeyondConfiguration;
+            }
+        } catch (NumberFormatException e) {
+            // ignore
+        }
+        return configuration;
     }
 
     protected Option launchpad() {

-- 
To stop receiving notification emails like this one, please contact
"commits@sling.apache.org" <co...@sling.apache.org>.