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:="(&(osgi.implementation=osgi.http)(version>=1.0)(!(version>=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
-