SLING-8915 Lazily replace previously cached ResourceBundles when reloaded on preload enabled
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..0fa18e5
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Apache Software Foundation Code of Conduct
+====
+
+Being an Apache project, Apache Sling adheres to the Apache Software Foundation's [Code of Conduct](https://www.apache.org/foundation/policies/conduct.html).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..ac82a1a
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,24 @@
+<!--/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+  ~ 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.
+  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/-->
+Contributing
+====
+
+Thanks for choosing to contribute!
+
+You will find all the necessary details about how you can do this at https://sling.apache.org/contributing.html.
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..f582519
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+slingOsgiBundleBuild()
diff --git a/README.md b/README.md
index 849e4f9..a91700d 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,7 @@
+[<img src="https://sling.apache.org/res/logos/sling.png"/>](https://sling.apache.org)
+
+ [![Build Status](https://builds.apache.org/buildStatus/icon?job=Sling/sling-org-apache-sling-i18n/master)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-i18n/job/master) [![Test Status](https://img.shields.io/jenkins/t/https/builds.apache.org/job/Sling/job/sling-org-apache-sling-i18n/job/master.svg)](https://builds.apache.org/job/Sling/job/sling-org-apache-sling-i18n/job/master/test_results_analyzer/) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.apache.sling/org.apache.sling.i18n/badge.svg)](https://search.maven.org/#search%7Cga%7C1%7Cg%3A%22org.apache.sling%22%20a%3A%22org.apache.sling.i18n%22) [![JavaDocs](https://www.javadoc.io/badge/org.apache.sling/org.apache.sling.i18n.svg)](https://www.javadoc.io/doc/org.apache.sling/org.apache.sling.i18n) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
+
 # Apache Sling I18N Support
 
 This module is part of the [Apache Sling](https://sling.apache.org) project.
diff --git a/bnd.bnd b/bnd.bnd
new file mode 100644
index 0000000..24986a0
--- /dev/null
+++ b/bnd.bnd
@@ -0,0 +1,16 @@
+Conditional-Package:\
+  org.apache.sling.commons.osgi
+
+Require-Capability:\
+  osgi.implementation;filter:="(&(osgi.implementation=osgi.http)(version>=1.0)(!(version>=2.0)))"
+
+Sling-Nodetypes:\
+  SLING-INF/nodetypes/jcrlanguage.cnd,\
+  SLING-INF/nodetypes/message.cnd
+
+-includeresource:\
+  @jackrabbit-jcr-commons-*.jar!/(org/apache/jackrabbit/util/ISO9075.*|org/apache/jackrabbit/util/XMLChar.*|org/apache/jackrabbit/util/Text.*|org/apache/jackrabbit/commons/json/Json*)
+
+-removeheaders:\
+  Include-Resource,\
+  Private-Package
diff --git a/pom.xml b/pom.xml
index a2a51b9..9fee8f4 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="ISO-8859-1"?>
+<?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
@@ -20,68 +20,40 @@
 <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/maven-v4_0_0.xsd">
 
     <modelVersion>4.0.0</modelVersion>
+
     <parent>
         <groupId>org.apache.sling</groupId>
-        <artifactId>sling</artifactId>
-        <version>30</version>
+        <artifactId>sling-bundle-parent</artifactId>
+        <version>35</version>
         <relativePath />
     </parent>
 
     <artifactId>org.apache.sling.i18n</artifactId>
-    <packaging>bundle</packaging>
-    <version>2.5.10</version>
+    <version>2.5.15-SNAPSHOT</version>
 
     <name>Apache Sling I18N Support</name>
-    <description>
-        Support for creating Java I18N ResourceBundles from repository
-        resources.
-    </description>
+    <description>Support for creating Java I18N ResourceBundles from repository resources.</description>
 
     <properties>
-        <exam.version>4.9.1</exam.version>
-        <org.ops4j.pax.logging.DefaultServiceLog.level>INFO</org.ops4j.pax.logging.DefaultServiceLog.level>
+        <org.ops4j.pax.exam.version>4.13.1</org.ops4j.pax.exam.version>
     </properties>
 
     <scm>
         <connection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-i18n.git</connection>
         <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/sling-org-apache-sling-i18n.git</developerConnection>
         <url>https://gitbox.apache.org/repos/asf?p=sling-org-apache-sling-i18n.git</url>
-      <tag>org.apache.sling.i18n-2.5.10</tag>
-  </scm>
+        <tag>HEAD</tag>
+    </scm>
 
     <build>
         <plugins>
             <plugin>
-                <groupId>org.apache.felix</groupId>
-                <artifactId>maven-bundle-plugin</artifactId>
-                <extensions>true</extensions>
-                <configuration>
-                    <instructions>
-                        <Sling-Nodetypes>
-                            SLING-INF/nodetypes/jcrlanguage.cnd,
-                            SLING-INF/nodetypes/message.cnd
-                        </Sling-Nodetypes>
-                        <!-- embed the commons.osgi bundle as described in http://njbartlett.name/2014/05/26/static-linking.html,
-                             to make this bundle compatible with older versions of Sling -->
-                        <Conditional-Package>org.apache.sling.commons.osgi</Conditional-Package>
-                        <Embed-Dependency>
-                            jackrabbit-jcr-commons;inline="org/apache/jackrabbit/util/ISO9075.*|org/apache/jackrabbit/util/XMLChar.*|org/apache/jackrabbit/util/Text.*|org/apache/jackrabbit/commons/json/Json*"
-                        </Embed-Dependency>
-                        <Require-Capability>
-                            osgi.implementation;filter:="(&amp;(osgi.implementation=osgi.http)(version&gt;=1.0)(!(version&gt;=2.0)))"
-                        </Require-Capability>
-                    </instructions>
-                </configuration>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-maven-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.apache.rat</groupId>
-                <artifactId>apache-rat-plugin</artifactId>
-                <configuration>
-                    <excludes>
-                        <exclude>derby.log</exclude>
-                        <exclude>jackrabbit/**</exclude>
-                    </excludes>
-                </configuration>
+                <groupId>biz.aQute.bnd</groupId>
+                <artifactId>bnd-baseline-maven-plugin</artifactId>
             </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
@@ -97,26 +69,6 @@
                 <artifactId>maven-source-plugin</artifactId>
             </plugin>
             <plugin>
-                <groupId>org.codehaus.mojo</groupId>
-                <artifactId>build-helper-maven-plugin</artifactId>
-                <executions>
-                    <execution>
-                        <id>reserve-network-port</id>
-                        <goals>
-                            <goal>reserve-network-port</goal>
-                        </goals>
-                        <phase>pre-integration-test</phase>
-                        <configuration>
-                            <portNames>
-                                <portName>http.port</portName>
-                            </portNames>
-                            <minPortNumber>45000</minPortNumber>
-                            <maxPortNumber>45999</maxPortNumber>
-                        </configuration>
-                    </execution>
-                </executions>
-            </plugin>
-            <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-failsafe-plugin</artifactId>
                 <executions>
@@ -128,46 +80,52 @@
                     </execution>
                 </executions>
                 <configuration>
-                    <systemPropertyVariables>
-                        <org.ops4j.pax.logging.DefaultServiceLog.level>${org.ops4j.pax.logging.DefaultServiceLog.level}</org.ops4j.pax.logging.DefaultServiceLog.level>
-                        <pax.exam.log.level>${pax.exam.log.level}</pax.exam.log.level>
-                        <java.protocol.handler.pkgs>org.ops4j.pax.url</java.protocol.handler.pkgs>
-                        <bundle.filename>${basedir}/target/${project.build.finalName}.jar</bundle.filename>
-                        <bundle.build.dir>${basedir}/target</bundle.build.dir>
-                        <org.osgi.service.http.port>${http.port}</org.osgi.service.http.port>
-                    </systemPropertyVariables>
+                    <redirectTestOutputToFile>true</redirectTestOutputToFile>
+                    <systemProperties>
+                        <property>
+                            <name>bundle.filename</name>
+                            <value>${basedir}/target/${project.build.finalName}.jar</value>
+                        </property>
+                    </systemProperties>
                 </configuration>
             </plugin>
             <plugin>
-                <artifactId>maven-clean-plugin</artifactId>
-                <configuration>
-                    <filesets>
-                        <fileset>
-                            <directory>${basedir}</directory>
-                            <includes>
-                                <include>oak/**</include>
-                                <include>jackrabbit/**</include>
-                                <include>derby.log</include>
-                            </includes>
-                        </fileset>
-                    </filesets>
-                </configuration>
+                <groupId>org.apache.servicemix.tooling</groupId>
+                <artifactId>depends-maven-plugin</artifactId>
             </plugin>
         </plugins>
     </build>
+
     <dependencies>
+        <!-- javax -->
         <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
+            <groupId>javax.inject</groupId>
+            <artifactId>javax.inject</artifactId>
             <scope>test</scope>
         </dependency>
         <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>javax.servlet-api</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <!-- OSGi -->
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.annotation.versioning</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.osgi</groupId>
             <artifactId>osgi.core</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
             <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.component.annotations</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
             <artifactId>org.osgi.service.http.whiteboard</artifactId>
             <version>1.0.0</version>
             <scope>provided</scope>
@@ -185,8 +143,8 @@
             <scope>test</scope>
         </dependency>
         <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>javax.servlet-api</artifactId>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.service.metatype.annotations</artifactId>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -223,6 +181,19 @@
             <version>1.2.0</version>
             <scope>provided</scope>
         </dependency>
+        <dependency>
+            <groupId>org.apache.sling</groupId>
+            <artifactId>org.apache.sling.testing.paxexam</artifactId>
+            <version>3.0.0</version>
+            <scope>test</scope>
+        </dependency>
+        <!-- Apache Felix -->
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.framework</artifactId>
+            <version>6.0.3</version>
+            <scope>test</scope>
+        </dependency>
         <!-- Testing -->
         <dependency>
             <groupId>javax.jcr</groupId>
@@ -273,46 +244,40 @@
             <version>1.2.17</version>
             <scope>test</scope>
         </dependency>
+        <!-- testing -->
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam</artifactId>
-            <version>${exam.version}</version>
+            <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-cm</artifactId>
-            <version>${exam.version}</version>
+            <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-container-forked</artifactId>
-            <version>${exam.version}</version>
+            <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-junit4</artifactId>
-            <version>${exam.version}</version>
+            <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
         </dependency>
         <dependency>
             <groupId>org.ops4j.pax.exam</groupId>
             <artifactId>pax-exam-link-mvn</artifactId>
-            <version>${exam.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.felix</groupId>
-            <artifactId>org.apache.felix.framework</artifactId>
-            <version>5.4.0</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.apache.sling</groupId>
-            <artifactId>org.apache.sling.paxexam.util</artifactId>
-            <version>1.0.2</version>
+            <version>${org.ops4j.pax.exam.version}</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java b/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
index f1abb04..a791954 100644
--- a/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
+++ b/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
@@ -72,22 +72,21 @@
 
     private final String baseName;
 
-    private final Set<String> languageRoots = new HashSet<String>();
+    private final Set<String> languageRoots = new HashSet<>();
 
-    JcrResourceBundle(Locale locale, String baseName,
-            ResourceResolver resourceResolver) {
+    JcrResourceBundle(final Locale locale, final String baseName,
+            final ResourceResolver resourceResolver) {
         this.locale = locale;
         this.baseName = baseName;
 
         log.info("Finding all dictionaries for '{}' (basename: {}) ...", locale, baseName == null ? "<none>" : baseName);
 
-        long start = System.currentTimeMillis();
-        resourceResolver.refresh();
-        Set<String> roots = loadPotentialLanguageRoots(resourceResolver, locale, baseName);
+        final long start = System.currentTimeMillis();
+        final Set<String> roots = loadPotentialLanguageRoots(resourceResolver, locale, baseName);
         this.resources = loadFully(resourceResolver, roots, this.languageRoots);
 
-        long end = System.currentTimeMillis();
         if (log.isInfoEnabled()) {
+            final long end = System.currentTimeMillis();
             log.info(
                 "Finished loading {} entries for '{}' (basename: {}) in {}ms",
                 new Object[] { resources.size(), locale, baseName == null ? "<none>" : baseName, (end - start)}
@@ -177,7 +176,7 @@
         //   [2] /libs   -> [dict6, ...]
         //   [3] (other) -> [dict7, dict8 ...]
 
-        List<List<Map<String, Object>>> dictionariesBySearchPath = new ArrayList<List<Map<String, Object>>>(searchPath.length + 1);
+        List<List<Map<String, Object>>> dictionariesBySearchPath = new ArrayList<>(searchPath.length + 1);
         for (int i = 0; i < searchPath.length + 1; i++) {
             dictionariesBySearchPath.add(new ArrayList<Map<String, Object>>());
         }
@@ -191,7 +190,7 @@
             }
 
             // linked hash map to keep order (not functionally important, but helpful for dictionary debugging)
-            Map<String, Object> dictionary = new LinkedHashMap<String, Object>();
+            Map<String, Object> dictionary = new LinkedHashMap<>();
 
             // find where in the search path this dict belongs
             // otherwise put it in the outside-the-search-path bucket (last list)
@@ -215,7 +214,7 @@
         }
 
         // linked hash map to keep order (not functionally important, but helpful for dictionary debugging)
-        final Map<String, Object> result = new LinkedHashMap<String, Object>();
+        final Map<String, Object> result = new LinkedHashMap<>();
 
         // first, add everything that's not under a search path (e.g. /content)
         // below, same strings inside a search path dictionary would overlay them since
@@ -325,7 +324,7 @@
         final String localeRFC4646String = toRFC4646String(locale);
         final String localeRFC4646StringLower = localeRFC4646String.toLowerCase();
 
-        final Set<String> paths = new LinkedHashSet<String>();
+        final Set<String> paths = new LinkedHashSet<>();
         final Iterator<Resource> bundles = resourceResolver.findResources(QUERY_LANGUAGE_ROOTS, "xpath");
         while (bundles.hasNext()) {
             Resource bundle = bundles.next();
@@ -338,7 +337,7 @@
                         || language.equals(localeRFC4646StringLower)) {
                     // basename might be a multivalue (see https://issues.apache.org/jira/browse/SLING-4547)
                     String[] baseNames = properties.get(PROP_BASENAME, new String[]{});
-                    if (baseName == null || Arrays.asList(baseName).contains(baseName)) {
+                    if (baseName == null || Arrays.asList(baseNames).contains(baseName)) {
                         paths.add(bundle.getPath());
                     }
                 }
diff --git a/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundleProvider.java b/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundleProvider.java
index 3cd147c..c51cf9f 100644
--- a/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundleProvider.java
+++ b/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundleProvider.java
@@ -73,7 +73,7 @@
  */
 @Component(service = {ResourceBundleProvider.class, ResourceChangeListener.class},
     property = {
-            Constants.SERVICE_DESCRIPTION + "=I18n Resource Bundle Provider",
+            Constants.SERVICE_DESCRIPTION + "=Apache Sling I18n Resource Bundle Provider",
             Constants.SERVICE_VENDOR + "=The Apache Software Foundation",
             ResourceChangeListener.PATHS + "=/"
     })
@@ -86,12 +86,8 @@
      */
     private static final Pattern USER_ASSIGNED_COUNTRY_CODES_PATTERN = Pattern.compile("aa|q[m-z]|x[a-z]|zz");
 
-    @ObjectClassDefinition(name ="Apache Sling I18N ResourceBundle Provider",
-            description ="ResourceBundleProvider service which loads the messages "+
-                 "from the repository. If the user name field is left empty, the provider will "+
-                 "log into the repository as the administrative user. Otherwise the given user "+
-                 "name and password are used to access the repository. Failing to access the "+
-                 "repository, effectively disables the provider.")
+    @ObjectClassDefinition(name ="Apache Sling I18N Resource Bundle Provider",
+            description ="ResourceBundleProvider service which loads the messages from the repository.")
     public @interface Config {
 
         @AttributeDefinition(name = "Default Locale",
@@ -131,13 +127,7 @@
      * configuration property. This defaults to <code>Locale.ENGLISH</code> if
      * the configuration property is not set.
      */
-    private Locale defaultLocale = Locale.ENGLISH;
-
-    /**
-     * The resource resolver used to access the resource bundles. This object is
-     * retrieved from the {@link #resourceResolverFactory} using the service user session.
-     */
-    private ResourceResolver resourceResolver;
+    private volatile Locale defaultLocale = Locale.ENGLISH;
 
     /**
      * Map of cached resource bundles indexed by a key combined of the base name
@@ -156,9 +146,9 @@
      * Return root resource bundle as created on-demand by
      * {@link #getRootResourceBundle()}.
      */
-    private ResourceBundle rootResourceBundle;
+    private volatile ResourceBundle rootResourceBundle;
 
-    private BundleContext bundleContext;
+    private volatile BundleContext bundleContext;
 
     /**
      * Each ResourceBundle is registered as a service. Each registration is stored in this map with the locale & base name used as a key.
@@ -169,6 +159,10 @@
 
     private long invalidationDelay;
 
+    private ResourceResolver createResourceResolver() throws LoginException {
+        return resourceResolverFactory.getServiceResourceResolver(null);
+    }
+
     // ---------- ResourceBundleProvider ---------------------------------------
 
     /**
@@ -193,67 +187,93 @@
      *             is not available to access the resources.
      */
     @Override
-    public ResourceBundle getResourceBundle(Locale locale) {
+    public ResourceBundle getResourceBundle(final Locale locale) {
         return getResourceBundle(null, locale);
     }
 
     @Override
-    public ResourceBundle getResourceBundle(String baseName, Locale locale) {
-        return getResourceBundleInternal(baseName, locale);
+    public ResourceBundle getResourceBundle(final String baseName, Locale locale) {
+        return getResourceBundleInternal(null, baseName, locale);
     }
 
-    // ---------- EventHandler ------------------------------------------------
+    // ---------- ResourceChangeListener ------------------------------------------------
+
+    private static final class ChangeStatus {
+        public ResourceResolver resourceResolver;
+        public boolean reloadAll = false;
+        public final Set<JcrResourceBundle> reloadBundles = new HashSet<>();
+    }
 
     @Override
-    public void onChange(List<ResourceChange> changes) {
-        boolean refreshed = false;
-        for(final ResourceChange change : changes) {
-            log.trace("handleEvent: Detecting event {} for path '{}'", change.getType(), change.getPath());
-
-            // if this change was on languageRootPath level this might change basename and locale as well, therefore
-            // invalidate everything
-            if (languageRootPaths.contains(change.getPath())) {
-                log.debug(
-                        "handleEvent: Detected change of cached language root '{}', removing all cached ResourceBundles",
-                        change.getPath());
-                scheduleReloadBundles(true);
+    public void onChange(final List<ResourceChange> changes) {
+        final ChangeStatus status = new ChangeStatus();
+        try {
+            for (final ResourceChange change : changes) {
+                this.onChange(status, change);
+                // if we need to reload all, we can skip all other events
+                if ( status.reloadAll ) {
+                    break;
+                }
+            }
+            if ( status.reloadAll ) {
+                this.scheduleReloadBundles(true);
             } else {
-                // if it is only a change below a root path, only messages of one resource bundle can be affected!
-                for (final String root : languageRootPaths) {
-                    if (change.getPath().startsWith(root)) {
-                        // figure out which JcrResourceBundle from the cached ones is affected
-                        for (JcrResourceBundle bundle : resourceBundleCache.values()) {
-                            if (bundle.getLanguageRootPaths().contains(root)) {
-                                // reload it
-                                log.debug("handleEvent: Resource changes below '{}', reloading ResourceBundle '{}'",
-                                        root, bundle);
-                                scheduleReloadBundle(bundle);
-                                return;
-                            }
-                        }
-                        log.debug("handleEvent: No cached resource bundle found with root '{}'", root);
-                        break;
-                    }
+                for(final JcrResourceBundle bundle : status.reloadBundles ) {
+                    this.scheduleReloadBundle(bundle);
                 }
-                // may be a completely new dictionary
-                if (!refreshed) {
-                    // refresh at most once per onChange()
-                    resourceResolver.refresh();
-                    refreshed = true;
-                }
-                if (isDictionaryResource(change)) {
-                    scheduleReloadBundles(true);
-                }
+            }
+        } catch ( final LoginException le) {
+            log.error("Unable to get service resource resolver.", le);
+        } finally {
+            if ( status.resourceResolver != null ) {
+                status.resourceResolver.close();
             }
         }
     }
 
-    private boolean isDictionaryResource(final ResourceChange change) {
+    private void onChange(final ChangeStatus status, final ResourceChange change)
+    throws LoginException {
+        log.debug("onChange: Detecting change {} for path '{}'", change.getType(), change.getPath());
+
+        // if this change was on languageRootPath level this might change basename and locale as well, therefore
+        // invalidate everything
+        if (languageRootPaths.contains(change.getPath())) {
+            log.debug(
+                    "onChange: Detected change of cached language root '{}', removing all cached ResourceBundles",
+                    change.getPath());
+            status.reloadAll = true;
+        } else {
+            for (final String root : languageRootPaths) {
+                if (change.getPath().startsWith(root)) {
+                    // figure out which JcrResourceBundles from the cached ones is affected
+                    for (JcrResourceBundle bundle : resourceBundleCache.values()) {
+                        if (bundle.getLanguageRootPaths().contains(root)) {
+                            // reload it
+                            log.debug("onChange: Resource changes below '{}', reloading ResourceBundle '{}'",
+                                    root, bundle);
+                            status.reloadBundles.add(bundle);
+                        }
+                    }
+                }
+            }
+
+            // may be a completely new dictionary
+            if ( status.resourceResolver == null ) {
+                status.resourceResolver = createResourceResolver() ;
+            }
+            if (isDictionaryResource(status.resourceResolver, change)) {
+                status.reloadAll = true;
+            }
+        }
+    }
+
+
+    private boolean isDictionaryResource(final ResourceResolver resolver, final ResourceChange change) {
         // language node changes happen quite frequently (https://issues.apache.org/jira/browse/SLING-2881)
         // therefore only consider changes either for sling:MessageEntry's
         // or for JSON dictionaries
         // get valuemap
-        final Resource resource = resourceResolver.getResource(change.getPath());
+        final Resource resource = resolver.getResource(change.getPath());
         if (resource == null) {
             log.trace("Could not get resource for '{}' for event {}", change.getPath(), change.getType());
             return false;
@@ -293,7 +313,7 @@
         return false;
     }
 
-    private void scheduleReloadBundles(boolean withDelay) {
+    private void scheduleReloadBundles(final boolean withDelay) {
         // cancel all reload individual bundle jobs!
         synchronized(scheduledJobNames) {
             for (String scheduledJobName : scheduledJobNames) {
@@ -308,7 +328,7 @@
         } else {
             options = scheduler.NOW();
         }
-        options.name("JcrResourceBundleProvider: reload all resource bundles");
+        options.name("ResourceBundleProvider: reload all resource bundles");
         scheduler.schedule(new Runnable() {
             @Override
             public void run() {
@@ -319,14 +339,12 @@
         }, options);
     }
 
-    private void scheduleReloadBundle(JcrResourceBundle bundle) {
-        String baseName = bundle.getBaseName();
-        Locale locale = bundle.getLocale();
-        final Key key = new Key(baseName, locale);
+    private void scheduleReloadBundle(final JcrResourceBundle bundle) {
+        final Key key = new Key(bundle.getBaseName(), bundle.getLocale());
 
         // defer this job
         ScheduleOptions options = scheduler.AT(new Date(System.currentTimeMillis() + invalidationDelay));
-        final String jobName = "JcrResourceBundleProvider: reload bundle with key " + key.toString();
+        final String jobName = "ResourceBundleProvider: reload bundle with key " + key.toString();
         scheduledJobNames.add(jobName);
         options.name(jobName);
         scheduler.schedule(new Runnable() {
@@ -365,7 +383,7 @@
 
         if (preloadBundles) {
             // reload the bundle from the repository (will also fill cache and register as a service)
-            getResourceBundleInternal(key.baseName, key.locale, true);
+            getResourceBundleInternal(null, key.baseName, key.locale, true);
         }
     }
 
@@ -378,14 +396,13 @@
      */
     @Activate
     protected void activate(final BundleContext context, final Config config) throws LoginException {
-        String localeString = config.locale_default();
+        final String localeString = config.locale_default();
         this.defaultLocale = toLocale(localeString);
         this.preloadBundles = config.preload_bundles();
 
         this.bundleContext = context;
-        invalidationDelay = config.invalidation_delay();
+        this.invalidationDelay = config.invalidation_delay();
         if (this.resourceResolverFactory != null) { // this is only null during test execution!
-            resourceResolver = resourceResolverFactory.getServiceResourceResolver(null);
             scheduleReloadBundles(false);
         }
 
@@ -394,7 +411,7 @@
     @Deactivate
     protected void deactivate() {
         clearCache();
-        resourceResolver.close();
+        this.bundleContext = null;
     }
 
     // ---------- internal -----------------------------------------------------
@@ -408,11 +425,11 @@
      *             created and the <code>ResourceResolver</code> is not
      *             available to access the resources.
      */
-    private ResourceBundle getResourceBundleInternal(String baseName, Locale locale) {
-        return getResourceBundleInternal(baseName, locale, false);
+    private ResourceBundle getResourceBundleInternal(ResourceResolver optionalResolver, String baseName, Locale locale) {
+        return getResourceBundleInternal(optionalResolver, baseName, locale, false);
     }
 
-    private ResourceBundle getResourceBundleInternal(String baseName, Locale locale, boolean overwriteCache) {
+    private ResourceBundle getResourceBundleInternal(ResourceResolver optionalResolver, final String baseName, Locale locale, final boolean overwriteCache) {
         if (locale == null) {
             locale = defaultLocale;
         }
@@ -433,13 +450,30 @@
                     log.debug("getResourceBundleInternal({}): got cache hit on second try", key);
                 } else {
                     log.debug("getResourceBundleInternal({}): reading from Repository", key);
-                    resourceBundle = createResourceBundle(key.baseName, key.locale);
-                    // put the newly created ResourceBundle to the cache. If it replaces an existing entry unregister the existing
-                    // service registration first before re-registering the new ResourceBundle.
-                    if (resourceBundleCache.put(key, resourceBundle) != null) {
-                        unregisterResourceBundle(key);
+                    ResourceResolver localResolver = null;
+                    try  {
+                        if ( optionalResolver == null ) {
+                            localResolver = createResourceResolver();
+                            optionalResolver = localResolver;
+                        }
+
+                        resourceBundle = createResourceBundle(optionalResolver, key.baseName, key.locale);
+                        // put the newly created ResourceBundle to the cache. If it replaces an existing entry unregister the existing
+                        // service registration first before re-registering the new ResourceBundle.
+                        if (resourceBundleCache.put(key, resourceBundle) != null) {
+                            unregisterResourceBundle(key);
+                        }
+                        registerResourceBundle(key, resourceBundle);
+
+                    } catch ( final LoginException le) {
+                        throw (MissingResourceException)new MissingResourceException("Unable to create service resource resolver",
+                                baseName,
+                                locale.toString()).initCause(le);
+                    } finally {
+                        if ( localResolver != null ) {
+                            localResolver.close();
+                        }
                     }
-                    registerResourceBundle(key, resourceBundle);
                 }
             } catch (InterruptedException e) {
                 Thread.interrupted();
@@ -489,13 +523,13 @@
      * @throws MissingResourceException If the <code>ResourceResolver</code>
      *             is not available to access the resources.
      */
-    private JcrResourceBundle createResourceBundle(String baseName, Locale locale) {
-        final JcrResourceBundle bundle = new JcrResourceBundle(locale, baseName, resourceResolver);
+    private JcrResourceBundle createResourceBundle(final ResourceResolver resolver, final String baseName, final Locale locale) {
+        final JcrResourceBundle bundle = new JcrResourceBundle(locale, baseName, resolver);
 
         // set parent resource bundle
         Locale parentLocale = getParentLocale(locale);
         if (parentLocale != null) {
-            bundle.setParent(getResourceBundleInternal(baseName, parentLocale));
+            bundle.setParent(getResourceBundleInternal(resolver, baseName, parentLocale));
         } else {
             bundle.setParent(getRootResourceBundle());
         }
@@ -553,33 +587,38 @@
         resourceBundleCache.clear();
         languageRootPaths.clear();
 
+        final List<ServiceRegistration<ResourceBundle>> regs;
         synchronized (this) {
-            for (ServiceRegistration<ResourceBundle> serviceReg : bundleServiceRegistrations.values()) {
-                serviceReg.unregister();
-            }
+            regs = new ArrayList<>(bundleServiceRegistrations.values());
             bundleServiceRegistrations.clear();
         }
+        for (final ServiceRegistration<ResourceBundle> serviceReg : regs) {
+            serviceReg.unregister();
+        }
     }
 
     private void preloadBundles() {
         if (preloadBundles) {
-            resourceResolver.refresh();
-            Iterator<Map<String, Object>> bundles = resourceResolver.queryResources(
+            try ( final ResourceResolver resolver = createResourceResolver() ) {
+                final Iterator<Map<String, Object>> bundles = resolver.queryResources(
                     JcrResourceBundle.QUERY_LANGUAGE_ROOTS, "xpath");
-            Set<Key> usedKeys = new HashSet<>();
-            while (bundles.hasNext()) {
-                Map<String,Object> bundle = bundles.next();
-                if (bundle.containsKey(PROP_LANGUAGE)) {
-                    Locale locale = toLocale(bundle.get(PROP_LANGUAGE).toString());
-                    String baseName = null;
-                    if (bundle.containsKey(PROP_BASENAME)) {
-                        baseName = bundle.get(PROP_BASENAME).toString();
-                    }
-                    Key key = new Key(baseName, locale);
-                    if (usedKeys.add(key)) {
-                        getResourceBundle(baseName, locale);
+                final Set<Key> usedKeys = new HashSet<>();
+                while (bundles.hasNext()) {
+                    final Map<String,Object> bundle = bundles.next();
+                    if (bundle.containsKey(PROP_LANGUAGE)) {
+                        final Locale locale = toLocale(bundle.get(PROP_LANGUAGE).toString());
+                        String baseName = null;
+                        if (bundle.containsKey(PROP_BASENAME)) {
+                            baseName = bundle.get(PROP_BASENAME).toString();
+                        }
+                        final Key key = new Key(baseName, locale);
+                        if (usedKeys.add(key)) {
+                            getResourceBundleInternal(resolver, baseName, locale);
+                        }
                     }
                 }
+            } catch ( final LoginException le) {
+                log.error("Unable to create service user resource resolver.", le);
             }
         }
     }
diff --git a/src/main/java/org/apache/sling/i18n/impl/RootResourceBundle.java b/src/main/java/org/apache/sling/i18n/impl/RootResourceBundle.java
index 74cabfb..05e3a04 100644
--- a/src/main/java/org/apache/sling/i18n/impl/RootResourceBundle.java
+++ b/src/main/java/org/apache/sling/i18n/impl/RootResourceBundle.java
@@ -18,9 +18,9 @@
  */
 package org.apache.sling.i18n.impl;
 
+import java.util.Collections;
 import java.util.Enumeration;
 import java.util.Locale;
-import java.util.NoSuchElementException;
 import java.util.ResourceBundle;
 
 /**
@@ -38,18 +38,6 @@
  */
 public class RootResourceBundle extends ResourceBundle {
 
-    // The empty enumeration returned fomr getKeys()
-    private final Enumeration<String> EMPTY = new Enumeration<String>() {
-
-        public boolean hasMoreElements() {
-            return false;
-        }
-
-        public String nextElement() {
-            throw new NoSuchElementException();
-        }
-    };
-
     // The pseudo Locale returned by getLocale()
     private final Locale locale = new Locale("");
 
@@ -74,7 +62,6 @@
      */
     @Override
     public Enumeration<String> getKeys() {
-        return EMPTY;
+        return Collections.emptyEnumeration();
     }
-
 }
diff --git a/src/test/java/org/apache/sling/i18n/impl/ConcurrentJcrResourceBundleLoadingTest.java b/src/test/java/org/apache/sling/i18n/impl/ConcurrentJcrResourceBundleLoadingTest.java
index 9089b42..2127ee6 100644
--- a/src/test/java/org/apache/sling/i18n/impl/ConcurrentJcrResourceBundleLoadingTest.java
+++ b/src/test/java/org/apache/sling/i18n/impl/ConcurrentJcrResourceBundleLoadingTest.java
@@ -38,6 +38,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
+import org.apache.sling.api.resource.ResourceResolver;
 import org.apache.sling.i18n.impl.JcrResourceBundleProvider.Key;
 import org.junit.Before;
 import org.junit.Test;
@@ -103,8 +104,9 @@
                 return 5000;
             }
         });
-        doReturn(english).when(provider, "createResourceBundle", eq(null), eq(Locale.ENGLISH));
-        doReturn(german).when(provider, "createResourceBundle", eq(null), eq(Locale.GERMAN));
+        doReturn(null).when(provider, "createResourceResolver");
+        doReturn(english).when(provider, "createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.ENGLISH));
+        doReturn(german).when(provider, "createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.GERMAN));
         Mockito.when(german.getLocale()).thenReturn(Locale.GERMAN);
         Mockito.when(english.getLocale()).thenReturn(Locale.ENGLISH);
         Mockito.when(german.getParent()).thenReturn(english);
@@ -117,7 +119,7 @@
         assertEquals(german, provider.getResourceBundle(Locale.GERMAN));
         assertEquals(german, provider.getResourceBundle(Locale.GERMAN));
 
-        verifyPrivate(provider, times(2)).invoke("createResourceBundle", eq(null), any(Locale.class));
+        verifyPrivate(provider, times(2)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), any(Locale.class));
     }
 
     @Test
@@ -136,8 +138,8 @@
         executor.shutdown();
         executor.awaitTermination(5, TimeUnit.SECONDS);
 
-        verifyPrivate(provider, times(1)).invoke("createResourceBundle", eq(null), eq(Locale.ENGLISH));
-        verifyPrivate(provider, times(1)).invoke("createResourceBundle", eq(null), eq(Locale.GERMAN));
+        verifyPrivate(provider, times(1)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.ENGLISH));
+        verifyPrivate(provider, times(1)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.GERMAN));
     }
 
     @Test
@@ -154,8 +156,8 @@
         provider.getResourceBundle(Locale.ENGLISH);
         provider.getResourceBundle(Locale.GERMAN);
 
-        verifyPrivate(provider, times(1)).invoke("createResourceBundle", eq(null), eq(Locale.ENGLISH));
-        verifyPrivate(provider, times(2)).invoke("createResourceBundle", eq(null), eq(Locale.GERMAN));
+        verifyPrivate(provider, times(1)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.ENGLISH));
+        verifyPrivate(provider, times(2)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.GERMAN));
     }
 
     @Test
@@ -172,8 +174,8 @@
         provider.getResourceBundle(Locale.ENGLISH);
         provider.getResourceBundle(Locale.GERMAN);
 
-        verifyPrivate(provider, times(2)).invoke("createResourceBundle", eq(null), eq(Locale.ENGLISH));
-        verifyPrivate(provider, times(2)).invoke("createResourceBundle", eq(null), eq(Locale.GERMAN));
+        verifyPrivate(provider, times(2)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.ENGLISH));
+        verifyPrivate(provider, times(2)).invoke("createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.GERMAN));
     }
 
     /**
@@ -194,7 +196,7 @@
                 newBundleReady.set(true);
                 return newBundle;
             }
-        }).when(provider, "createResourceBundle", eq(null), eq(Locale.ENGLISH));
+        }).when(provider, "createResourceBundle", any(ResourceResolver.class), eq(null), eq(Locale.ENGLISH));
 
         Executors.newScheduledThreadPool(1).scheduleAtFixedRate(new Runnable() {
             @Override
diff --git a/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java b/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java
new file mode 100644
index 0000000..3b31b89
--- /dev/null
+++ b/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java
@@ -0,0 +1,62 @@
+/*
+ * 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.i18n.it;
+
+import org.apache.sling.testing.paxexam.TestSupport;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+
+import static org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
+import static org.ops4j.pax.exam.CoreOptions.composite;
+import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
+import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
+
+public abstract class I18nTestSupport extends TestSupport {
+
+    @Configuration
+    public Option[] configuration() {
+        return new Option[]{
+            baseConfiguration(),
+            quickstart(),
+            // Sling I18N
+            testBundle("bundle.filename"),
+            factoryConfiguration("org.apache.sling.jcr.repoinit.RepositoryInitializer")
+                .put("scripts", new String[]{"create service user sling-i18n\n\n  set ACL for sling-i18n\n\n    allow   jcr:read    on /\n\n  end"})
+                .asOption(),
+            factoryConfiguration("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended")
+                .put("user.mapping", new String[]{"org.apache.sling.i18n=sling-i18n"})
+                .asOption(),
+            // testing
+            newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
+                .put("whitelist.bundles.regexp", "PAXEXAM-PROBE-.*")
+                .asOption(),
+            junitBundles()
+        };
+    }
+
+    protected Option quickstart() {
+        final int httpPort = findFreePort();
+        final String workingDirectory = workingDirectory();
+        return composite(
+            slingQuickstartOakTar(workingDirectory, httpPort)
+        );
+    }
+
+}
diff --git a/src/test/java/org/apache/sling/i18n/it/ResourceBundleProviderIT.java b/src/test/java/org/apache/sling/i18n/it/ResourceBundleProviderIT.java
index 7e1dfa2..047d424 100644
--- a/src/test/java/org/apache/sling/i18n/it/ResourceBundleProviderIT.java
+++ b/src/test/java/org/apache/sling/i18n/it/ResourceBundleProviderIT.java
@@ -18,18 +18,6 @@
  */
 package org.apache.sling.i18n.it;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
-import static org.ops4j.pax.exam.CoreOptions.frameworkProperty;
-import static org.ops4j.pax.exam.CoreOptions.junitBundles;
-import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
-import static org.ops4j.pax.exam.CoreOptions.options;
-import static org.ops4j.pax.exam.CoreOptions.systemProperty;
-import static org.ops4j.pax.exam.CoreOptions.when;
-
-import java.io.File;
-import java.net.URISyntaxException;
 import java.util.Locale;
 import java.util.ResourceBundle;
 
@@ -44,30 +32,24 @@
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.ops4j.pax.exam.Configuration;
-import org.ops4j.pax.exam.CoreOptions;
-import org.ops4j.pax.exam.Option;
-import org.ops4j.pax.exam.cm.ConfigurationAdminOptions;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
 @RunWith(PaxExam.class)
 @ExamReactorStrategy(PerClass.class)
-public class ResourceBundleProviderIT {
-
-    private static final String BUNDLE_JAR_SYS_PROP = "bundle.filename";
-
-    /** The property containing the build directory. */
-    private static final String SYS_PROP_BUILD_DIR = "bundle.build.dir";
-
-    private static final String DEFAULT_BUILD_DIR = "target";
-
-    private static final String PORT_CONFIG = "org.osgi.service.http.port";
+public class ResourceBundleProviderIT extends I18nTestSupport {
 
     public static final int RETRY_TIMEOUT_MSEC = 50000;
     public static final String MSG_KEY1 = "foo";
     public static final String MSG_KEY2 = "foo2";
+    public static final String MSG_KEY3 = "foo3";
+
+    public static final String BASENAME = "my-basename";
 
     @Inject
     private SlingRepository repository;
@@ -81,198 +63,24 @@
     private Node deDeRoot;
     private Node frRoot;
     private Node enRoot;
+    private Node enBasenameRoot;
 
-    @Configuration
-    public Option[] config() {
-        final String buildDir = System.getProperty(SYS_PROP_BUILD_DIR, DEFAULT_BUILD_DIR);
-        final String bundleFileName = System.getProperty( BUNDLE_JAR_SYS_PROP );
-        final File bundleFile = new File( bundleFileName );
-        if ( !bundleFile.canRead() ) {
-            throw new IllegalArgumentException( "Cannot read from bundle file " + bundleFileName + " specified in the "
-                + BUNDLE_JAR_SYS_PROP + " system property" );
-        }
-
-        String localRepo = System.getProperty("maven.repo.local", "");
-
-        final String jackrabbitVersion = "2.13.1";
-        final String oakVersion = "1.5.7";
-
-        final String slingHome = new File(buildDir + File.separatorChar + "sling_" + System.currentTimeMillis()).getAbsolutePath();
-
-        return options(
-                frameworkProperty("sling.home").value(slingHome),
-                frameworkProperty("repository.home").value(slingHome + File.separatorChar + "repository"),
-                when( localRepo.length() > 0 ).useOptions(
-                        systemProperty("org.ops4j.pax.url.mvn.localRepository").value(localRepo)
-                ),
-                when( System.getProperty(PORT_CONFIG) != null ).useOptions(
-                        systemProperty(PORT_CONFIG).value(System.getProperty(PORT_CONFIG))),
-                systemProperty("pax.exam.osgi.unresolved.fail").value("true"),
-
-                ConfigurationAdminOptions.newConfiguration("org.apache.felix.jaas.ConfigurationSpi")
-                    .create(true)
-                    .put("jaas.defaultRealmName", "jackrabbit.oak")
-                    .put("jaas.configProviderName", "FelixJaasProvider")
-                    .asOption(),
-                ConfigurationAdminOptions.factoryConfiguration("org.apache.felix.jaas.Configuration.factory")
-                    .create(true)
-                    .put("jaas.controlFlag", "optional")
-                    .put("jaas.classname", "org.apache.jackrabbit.oak.spi.security.authentication.GuestLoginModule")
-                    .put("jaas.ranking", 300)
-                    .asOption(),
-                ConfigurationAdminOptions.factoryConfiguration("org.apache.felix.jaas.Configuration.factory")
-                    .create(true)
-                    .put("jaas.controlFlag", "required")
-                    .put("jaas.classname", "org.apache.jackrabbit.oak.security.authentication.user.LoginModuleImpl")
-                    .asOption(),
-                ConfigurationAdminOptions.factoryConfiguration("org.apache.felix.jaas.Configuration.factory")
-                    .create(true)
-                    .put("jaas.controlFlag", "sufficient")
-                    .put("jaas.classname", "org.apache.jackrabbit.oak.security.authentication.token.TokenLoginModule")
-                    .put("jaas.ranking", 200)
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.jackrabbit.oak.security.authentication.AuthenticationConfigurationImpl")
-                    .create(true)
-                    .put("org.apache.jackrabbit.oak.authentication.configSpiName", "FelixJaasProvider")
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.jackrabbit.oak.security.user.UserConfigurationImpl")
-                    .create(true)
-                    .put("groupsPath", "/home/groups")
-                    .put("usersPath", "/home/users")
-                    .put("defaultPath", "1")
-                    .put("importBehavior", "besteffort")
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.jackrabbit.oak.security.user.RandomAuthorizableNodeName")
-                    .create(true)
-                    .put("enabledActions", new String[] {"org.apache.jackrabbit.oak.spi.security.user.action.AccessControlAction"})
-                    .put("userPrivilegeNames", new String[] {"jcr:all"})
-                    .put("groupPrivilegeNames", new String[] {"jcr:read"})
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.jackrabbit.oak.spi.security.user.action.DefaultAuthorizableActionProvider")
-                    .create(true)
-                    .put("length", 21)
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.jackrabbit.oak.plugins.segment.SegmentNodeStoreService")
-                    .create(true)
-                    .put("name", "Default NodeStore")
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.sling.resourceresolver.impl.observation.OsgiObservationBridge")
-                    .create(true)
-                    .put("enabled", true)
-                    .asOption(),
-                ConfigurationAdminOptions.factoryConfiguration("org.apache.sling.serviceusermapping.impl.ServiceUserMapperImpl.amended")
-                    .create(true)
-                    .put("user.mapping", new String[]{"org.apache.sling.i18n=sling-i18n"})
-                    .asOption(),
-                ConfigurationAdminOptions.newConfiguration("org.apache.sling.jcr.repoinit.impl.RepositoryInitializer")
-                    .put("references", new String[]{references()})
-                    .asOption(),
-
-                // logging
-                systemProperty("pax.exam.logging").value("none"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.log", "4.0.6"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.logservice", "1.0.6"),
-                mavenBundle("org.slf4j", "slf4j-api", "1.7.13"),
-                mavenBundle("org.slf4j", "jcl-over-slf4j", "1.7.13"),
-                mavenBundle("org.slf4j", "log4j-over-slf4j", "1.7.13"),
-
-                mavenBundle("commons-io", "commons-io", "2.4"),
-                mavenBundle("commons-fileupload", "commons-fileupload", "1.3.1"),
-                mavenBundle("commons-collections", "commons-collections", "3.2.2"),
-                mavenBundle("commons-codec", "commons-codec", "1.10"),
-                mavenBundle("commons-lang", "commons-lang", "2.6"),
-                mavenBundle("org.apache.commons", "commons-lang3", "3.5"),
-                mavenBundle("commons-pool", "commons-pool", "1.6"),
-
-                mavenBundle("org.apache.servicemix.bundles", "org.apache.servicemix.bundles.concurrent", "1.3.4_1"),
-
-                mavenBundle("org.apache.geronimo.bundles", "commons-httpclient", "3.1_1"),
-                mavenBundle("org.apache.tika", "tika-core", "1.9"),
-                mavenBundle("org.apache.tika", "tika-bundle", "1.9"),
-
-                // infrastructure
-                mavenBundle("org.apache.felix", "org.apache.felix.http.servlet-api", "1.1.2"),
-                mavenBundle("org.apache.felix", "org.apache.felix.http.jetty", "3.1.6"),
-                mavenBundle("org.apache.felix", "org.apache.felix.eventadmin", "1.4.4"),
-                mavenBundle("org.apache.felix", "org.apache.felix.scr", "2.0.4"),
-                mavenBundle("org.apache.felix", "org.apache.felix.configadmin", "1.8.10"),
-                mavenBundle("org.apache.felix", "org.apache.felix.inventory", "1.0.4"),
-                mavenBundle("org.apache.felix", "org.apache.felix.metatype", "1.1.2"),
-
-                // sling
-                mavenBundle("org.apache.sling", "org.apache.sling.settings", "1.3.8"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.osgi", "2.3.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.mime", "2.1.8"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.classloader", "1.3.2"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.scheduler", "2.4.14"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.threads", "3.2.4"),
-
-                mavenBundle("org.apache.sling", "org.apache.sling.auth.core", "1.3.12"),
-                mavenBundle("org.apache.sling", "org.apache.sling.discovery.api", "1.0.2"),
-                mavenBundle("org.apache.sling", "org.apache.sling.discovery.commons", "1.0.20"),
-                mavenBundle("org.apache.sling", "org.apache.sling.discovery.standalone", "1.0.2"),
-
-                mavenBundle("org.apache.sling", "org.apache.sling.api", "2.14.2"),
-                mavenBundle("org.apache.sling", "org.apache.sling.resourceresolver", "1.4.18"),
-                mavenBundle("org.apache.sling", "org.apache.sling.adapter", "2.1.10"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.resource", "2.8.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.classloader", "3.2.2"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.contentloader", "2.2.4"),
-                mavenBundle("org.apache.sling", "org.apache.sling.engine", "2.6.2"),
-                mavenBundle("org.apache.sling", "org.apache.sling.serviceusermapper", "1.3.2"),
-
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.jcr-wrapper", "2.0.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.api", "2.4.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.base", "2.4.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.repoinit", "1.1.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.repoinit.parser", "1.1.0"),
-                mavenBundle("org.apache.sling", "org.apache.sling.provisioning.model", "1.4.2"),
-
-                mavenBundle("com.google.guava", "guava", "15.0"),
-                mavenBundle("org.apache.jackrabbit", "jackrabbit-api", jackrabbitVersion),
-                mavenBundle("org.apache.jackrabbit", "jackrabbit-jcr-commons", jackrabbitVersion),
-                mavenBundle("org.apache.jackrabbit", "jackrabbit-spi", jackrabbitVersion),
-                mavenBundle("org.apache.jackrabbit", "jackrabbit-spi-commons", jackrabbitVersion),
-                mavenBundle("org.apache.jackrabbit", "jackrabbit-jcr-rmi", jackrabbitVersion),
-
-                mavenBundle("org.apache.felix", "org.apache.felix.jaas", "0.0.4"),
-
-                mavenBundle("org.apache.jackrabbit", "oak-core", oakVersion),
-                mavenBundle("org.apache.jackrabbit", "oak-commons", oakVersion),
-                mavenBundle("org.apache.jackrabbit", "oak-lucene", oakVersion),
-                mavenBundle("org.apache.jackrabbit", "oak-blob", oakVersion),
-                mavenBundle("org.apache.jackrabbit", "oak-jcr", oakVersion),
-
-                mavenBundle("org.apache.jackrabbit", "oak-segment", oakVersion),
-
-                mavenBundle("org.apache.sling", "org.apache.sling.jcr.oak.server", "1.1.0"),
-
-                mavenBundle("org.apache.sling", "org.apache.sling.testing.tools", "1.0.17-SNAPSHOT"),
-                mavenBundle("org.apache.sling", "org.apache.sling.commons.johnzon", "1.0.0"),
-                mavenBundle("org.apache.httpcomponents", "httpcore-osgi", "4.1.2"),
-                mavenBundle("org.apache.httpcomponents", "httpclient-osgi", "4.1.2"),
-
-                junitBundles(),
-
-                CoreOptions.bundle( bundleFile.toURI().toString() )
-           );
-    }
 
     static abstract class Retry {
         Retry(int timeoutMsec) {
             final long timeout = System.currentTimeMillis() + timeoutMsec;
             Throwable lastT = null;
-            while(System.currentTimeMillis() < timeout) {
+            while (System.currentTimeMillis() < timeout) {
                 try {
                     lastT = null;
                     exec();
                     break;
-                } catch(Throwable t) {
+                } catch (Throwable t) {
                     lastT = t;
                 }
             }
 
-            if(lastT != null) {
+            if (lastT != null) {
                 fail("Failed after " + timeoutMsec + " msec: " + lastT);
             }
         }
@@ -285,16 +93,17 @@
         session = repository.loginAdministrative(null);
         final Node root = session.getRootNode();
         final Node libs;
-        if(root.hasNode("libs")) {
-           libs = root.getNode("libs");
+        if (root.hasNode("libs")) {
+            libs = root.getNode("libs");
         } else {
-           libs = root.addNode("libs", "nt:unstructured");
+            libs = root.addNode("libs", "nt:unstructured");
         }
         i18nRoot = libs.addNode("i18n", "nt:unstructured");
         deRoot = addLanguageNode(i18nRoot, "de");
         frRoot = addLanguageNode(i18nRoot, "fr");
         deDeRoot = addLanguageNode(i18nRoot, "de_DE");
         enRoot = addLanguageNode(i18nRoot, "en");
+        enBasenameRoot = addLanguageNodeWithBasename(i18nRoot, "en", BASENAME);
         session.save();
     }
 
@@ -306,39 +115,45 @@
     }
 
     private Node addLanguageNode(Node parent, String language) throws RepositoryException {
-        final Node child = parent.addNode(language, "nt:folder");
+        final Node child = parent.addNode(language, "sling:Folder");
         child.addMixin("mix:language");
         child.setProperty("jcr:language", language);
         return child;
     }
 
-    private void assertMessages(final String key, final String deMessage, final String deDeMessage, final String frMessage) {
+    private Node addLanguageNodeWithBasename(Node parent, String language, String basename) throws RepositoryException {
+        final Node child = parent.addNode(language + "-" + basename, "sling:Folder");
+        child.addMixin("mix:language");
+        child.setProperty("jcr:language", language);
+        if (basename != null) {
+            child.setProperty("sling:basename", basename);
+        }
+        return child;
+    }
+
+    private void assertMessage(final String key, final Locale locale, final String basename, final String value) {
         new Retry(RETRY_TIMEOUT_MSEC) {
             @Override
             protected void exec() {
                 {
-                    final ResourceBundle deDE = resourceBundleProvider.getResourceBundle(Locale.GERMANY); // this is the resource bundle for de_DE
-                    assertNotNull(deDE);
-                    assertEquals(deDeMessage, deDE.getString(key));
-                }
-                {
-                    final ResourceBundle de = resourceBundleProvider.getResourceBundle(Locale.GERMAN);
-                    assertNotNull(de);
-                    assertEquals(deMessage, de.getString(key));
-                }
-                {
-                    final ResourceBundle fr = resourceBundleProvider.getResourceBundle(Locale.FRENCH);
-                    assertNotNull(fr);
-                    assertEquals(frMessage, fr.getString(key));
+                    final ResourceBundle resourceBundle = resourceBundleProvider.getResourceBundle(basename, locale); // this is the resource bundle for de_DE
+                    assertNotNull(resourceBundle);
+                    assertEquals(value, resourceBundle.getString(key));
                 }
             }
         };
     }
 
+    private void assertMessages(final String key, final String deMessage, final String deDeMessage, final String frMessage) {
+        assertMessage(key, Locale.GERMAN, null, deMessage);
+        assertMessage(key, Locale.GERMANY, null, deDeMessage);
+        assertMessage(key, Locale.FRENCH, null, frMessage);
+    }
+
     private void setMessage(final Node rootNode, final String key, final String message) throws RepositoryException {
         final String nodeName = "node_" + key;
         final Node node;
-        if ( rootNode.hasNode(nodeName) ) {
+        if (rootNode.hasNode(nodeName)) {
             node = rootNode.getNode(nodeName);
         } else {
             node = rootNode.addNode(nodeName, "sling:MessageEntry");
@@ -348,6 +163,18 @@
     }
 
     @Test
+    public void testGetResourceWithBasename() throws RepositoryException {
+        // set a key which available in the en dictionary without the basename
+        setMessage(enRoot, MSG_KEY1, "regular");
+        session.save();
+        // default key must be returned, as the one set above did not have the basename
+        assertMessage(MSG_KEY1, Locale.ENGLISH, BASENAME, MSG_KEY1);
+        setMessage(enBasenameRoot, MSG_KEY1, "overwritten");
+        session.save();
+        assertMessage(MSG_KEY1, Locale.ENGLISH, BASENAME, "overwritten");
+    }
+
+    @Test
     public void testChangesDetection() throws RepositoryException {
         // set a key which is only available in the en dictionary
         setMessage(enRoot, MSG_KEY2, "EN_message");
@@ -376,14 +203,18 @@
         setMessage(enRoot, MSG_KEY2, "EN_changed");
         session.save();
         assertMessages(MSG_KEY2, "EN_changed", "EN_changed", "EN_changed");
+
+        // set a message and fetch it so that it is cached in the resourcebundle cache
+        setMessage(enBasenameRoot, MSG_KEY3, "EN_basename_message");
+        session.save();
+        assertMessage(MSG_KEY3, Locale.ENGLISH, null, "EN_basename_message");
+        assertMessage(MSG_KEY3, Locale.ENGLISH, BASENAME, "EN_basename_message");
+
+        // see that both resource bundles with and without basename are changed
+        setMessage(enBasenameRoot, MSG_KEY3, "EN_basename_changed");
+        session.save();
+        assertMessage(MSG_KEY3, Locale.ENGLISH, null, "EN_basename_changed");
+        assertMessage(MSG_KEY3, Locale.ENGLISH, BASENAME, "EN_basename_changed");
     }
 
-    private String references() {
-        try {
-            String repoInitUrl = getClass().getResource("/repoinit.txt").toURI().toString();
-            return String.format("raw:%s", repoInitUrl);
-        } catch (URISyntaxException e) {
-            throw new RuntimeException("Failed to compute repoinit references", e);
-        }
-    }
 }
diff --git a/src/test/resources/repoinit.txt b/src/test/resources/repoinit.txt
deleted file mode 100644
index 57b44f8..0000000
--- a/src/test/resources/repoinit.txt
+++ /dev/null
@@ -1,23 +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.
-#
-################################################################################
-
-# sling-i18n
-
-create service user sling-i18n
-