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

enorman pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-i18n.git


The following commit(s) were added to refs/heads/master by this push:
     new 1eedfa6  SLING-12312 add support for Sling API 3.x and Jakarta servlet 
(#18)
1eedfa6 is described below

commit 1eedfa60279f2033426370c7a5cdafa7aa735733
Author: Eric Norman <[email protected]>
AuthorDate: Wed Aug 20 10:44:21 2025 -0700

    SLING-12312 add support for Sling API 3.x and Jakarta servlet (#18)
---
 pom.xml                                            |  26 ++-
 ...lver.java => DefaultJakartaLocaleResolver.java} |  23 +-
 .../apache/sling/i18n/DefaultLocaleResolver.java   |   9 +-
 ...lver.java => JakartaRequestLocaleResolver.java} |  12 +-
 .../java/org/apache/sling/i18n/LocaleResolver.java |   4 +-
 .../apache/sling/i18n/RequestLocaleResolver.java   |   2 +
 .../org/apache/sling/i18n/impl/I18NFilter.java     | 189 +++++++++-------
 .../sling/i18n/impl/LocaleResolverWrapper.java     |  61 ++++++
 .../i18n/impl/RequestLocaleResolverWrapper.java    |  51 +++++
 .../java/org/apache/sling/i18n/package-info.java   |   2 +-
 .../i18n/DefaultJakartaLocaleResolverTest.java     |  58 +++++
 .../sling/i18n/DefaultLocaleResolverTest.java      |  82 +++++++
 .../org/apache/sling/i18n/impl/I18NFilterTest.java | 241 +++++++++++++++++++++
 .../sling/i18n/impl/LocaleResolverWrapperTest.java |  85 ++++++++
 .../impl/RequestLocaleResolverWrapperTest.java     |  70 ++++++
 .../org/apache/sling/i18n/it/I18nTestSupport.java  |  28 +++
 .../sling/i18n/it/ResourceBundleLocatorIT.java     |  26 +--
 src/test/resources/exam.properties                 |  21 ++
 18 files changed, 863 insertions(+), 127 deletions(-)

diff --git a/pom.xml b/pom.xml
index 98d4dd2..920b9ac 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
     </parent>
 
     <artifactId>org.apache.sling.i18n</artifactId>
-    <version>2.6.7-SNAPSHOT</version>
+    <version>3.0.0-SNAPSHOT</version>
 
     <name>Apache Sling I18N Support</name>
     <description>Support for creating Java I18N ResourceBundles from 
repository resources.</description>
@@ -43,10 +43,11 @@
 
     <properties>
         
<project.build.outputTimestamp>2024-10-21T10:09:18Z</project.build.outputTimestamp>
-        <org.ops4j.pax.exam.version>4.13.3</org.ops4j.pax.exam.version>
+        <org.ops4j.pax.exam.version>4.14.0</org.ops4j.pax.exam.version>
         <jackrabbit.version>2.20.0</jackrabbit.version>
         <oak.version>1.22</oak.version>
-        <sling.java.version>8</sling.java.version>
+        <sling.java.version>17</sling.java.version>
+        <slf4j.version>2.0.17</slf4j.version>
     </properties>
 
     <dependencies>
@@ -54,6 +55,19 @@
         <dependency>
             <groupId>javax.servlet</groupId>
             <artifactId>javax.servlet-api</artifactId>
+            <version>4.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>jakarta.servlet</groupId>
+            <artifactId>jakarta.servlet-api</artifactId>
+            <version>6.0.0</version>
+            <scope>compile</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>org.apache.felix.http.wrappers</artifactId>
+            <version>1.1.10</version>
             <scope>provided</scope>
         </dependency>
         <!-- OSGi -->
@@ -106,7 +120,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.api</artifactId>
-            <version>2.16.4</version>
+            <version>3.0.0</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -118,6 +132,7 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
+            <version>${slf4j.version}</version>
             <scope>provided</scope>
         </dependency>
         <dependency>
@@ -136,7 +151,7 @@
         <dependency>
             <groupId>org.apache.sling</groupId>
             <artifactId>org.apache.sling.testing.paxexam</artifactId>
-            <version>4.0.0</version>
+            <version>4.1.2</version>
             <scope>test</scope>
         </dependency>
         <!-- Apache Felix -->
@@ -232,6 +247,7 @@
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-simple</artifactId>
+            <version>${slf4j.version}</version>
             <scope>test</scope>
         </dependency>
     </dependencies>
diff --git a/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java 
b/src/main/java/org/apache/sling/i18n/DefaultJakartaLocaleResolver.java
similarity index 64%
copy from src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java
copy to src/main/java/org/apache/sling/i18n/DefaultJakartaLocaleResolver.java
index 94f1935..e2d86c7 100644
--- a/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java
+++ b/src/main/java/org/apache/sling/i18n/DefaultJakartaLocaleResolver.java
@@ -18,14 +18,12 @@
  */
 package org.apache.sling.i18n;
 
-import javax.servlet.http.HttpServletRequest;
-
 import java.util.ArrayList;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Locale;
 
-import org.apache.sling.api.SlingHttpServletRequest;
+import jakarta.servlet.http.HttpServletRequest;
 
 /**
  * The <code>DefaultLocaleResolver</code> resolves the request's Locale by
@@ -33,25 +31,16 @@ import org.apache.sling.api.SlingHttpServletRequest;
  * will be the Servlet Container's implementation of this method and thus be
  * based on the client's <code>Accept-Language</code> header.
  */
-public class DefaultLocaleResolver implements LocaleResolver, 
RequestLocaleResolver {
-
-    /**
-     * Return the Locales provided by the
-     * <code>ServletRequest.getLocales()</code> method collected in a
-     * <code>List</code>.
-     */
-    public List<Locale> resolveLocale(final SlingHttpServletRequest request) {
-        return this.resolveLocale((HttpServletRequest) request);
-    }
+public class DefaultJakartaLocaleResolver implements 
JakartaRequestLocaleResolver {
 
     /**
-     * @see 
org.apache.sling.i18n.RequestLocaleResolver#resolveLocale(javax.servlet.http.HttpServletRequest)
+     * @see 
org.apache.sling.i18n.JakartaRequestLocaleResolver#resolveLocale(jakarta.servlet.http.HttpServletRequest)
      */
     public List<Locale> resolveLocale(final HttpServletRequest request) {
-        Enumeration<?> locales = request.getLocales();
-        ArrayList<Locale> localeList = new ArrayList<Locale>();
+        Enumeration<Locale> locales = request.getLocales();
+        ArrayList<Locale> localeList = new ArrayList<>();
         while (locales.hasMoreElements()) {
-            localeList.add((Locale) locales.nextElement());
+            localeList.add(locales.nextElement());
         }
         return localeList;
     }
diff --git a/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java 
b/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java
index 94f1935..3b1ab27 100644
--- a/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java
+++ b/src/main/java/org/apache/sling/i18n/DefaultLocaleResolver.java
@@ -32,7 +32,10 @@ import org.apache.sling.api.SlingHttpServletRequest;
  * calling the <code>ServletRequest.getLocales()</code> method, which generally
  * will be the Servlet Container's implementation of this method and thus be
  * based on the client's <code>Accept-Language</code> header.
+ *
+ * @deprecated use {@link DefaultJakartaLocaleResolver} instead
  */
+@Deprecated(since = "2.3.0")
 public class DefaultLocaleResolver implements LocaleResolver, 
RequestLocaleResolver {
 
     /**
@@ -48,10 +51,10 @@ public class DefaultLocaleResolver implements 
LocaleResolver, RequestLocaleResol
      * @see 
org.apache.sling.i18n.RequestLocaleResolver#resolveLocale(javax.servlet.http.HttpServletRequest)
      */
     public List<Locale> resolveLocale(final HttpServletRequest request) {
-        Enumeration<?> locales = request.getLocales();
-        ArrayList<Locale> localeList = new ArrayList<Locale>();
+        Enumeration<Locale> locales = request.getLocales();
+        ArrayList<Locale> localeList = new ArrayList<>();
         while (locales.hasMoreElements()) {
-            localeList.add((Locale) locales.nextElement());
+            localeList.add(locales.nextElement());
         }
         return localeList;
     }
diff --git a/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java 
b/src/main/java/org/apache/sling/i18n/JakartaRequestLocaleResolver.java
similarity index 87%
copy from src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java
copy to src/main/java/org/apache/sling/i18n/JakartaRequestLocaleResolver.java
index 898ecda..74f6b8b 100644
--- a/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java
+++ b/src/main/java/org/apache/sling/i18n/JakartaRequestLocaleResolver.java
@@ -18,23 +18,23 @@
  */
 package org.apache.sling.i18n;
 
-import javax.servlet.http.HttpServletRequest;
-
 import java.util.List;
 import java.util.Locale;
 
+import jakarta.servlet.http.HttpServletRequest;
+
 /**
- * The <code>RequestLocaleResolver</code> service interface may be implemented 
by a
+ * The <code>JakartaRequestLocaleResolver</code> service interface may be 
implemented by a
  * service registered under this name to allow the resolution of the request
  * <code>Locale</code> to apply.
  * <p>
  * This interface is intended to be implemented by providers knowing how to
  * resolve one or more <code>Locale</code>s applicable to handle the request.
  * <p>
- * Only a single <code>RequestLocaleResolver</code> service is currently used.
- * @since 2.2
+ * Only a single <code>JakartaRequestLocaleResolver</code> service is 
currently used.
+ * @since 3.0
  */
-public interface RequestLocaleResolver {
+public interface JakartaRequestLocaleResolver {
 
     /**
      * Return a non-<code>null</code> but possibly empty list of
diff --git a/src/main/java/org/apache/sling/i18n/LocaleResolver.java 
b/src/main/java/org/apache/sling/i18n/LocaleResolver.java
index 8937c7b..b0cb0ce 100644
--- a/src/main/java/org/apache/sling/i18n/LocaleResolver.java
+++ b/src/main/java/org/apache/sling/i18n/LocaleResolver.java
@@ -32,9 +32,9 @@ import org.apache.sling.api.SlingHttpServletRequest;
  * resolve one or more <code>Locale</code>s applicable to handle the request.
  * <p>
  * Only a single <code>LocaleResolver</code> service is currently used.
- * @deprecated The {@link RequestLocaleResolver} should be used instead.
+ * @deprecated The {@link JakartaRequestLocaleResolver} should be used instead.
  */
-@Deprecated
+@Deprecated(since = "2.2.0")
 public interface LocaleResolver {
 
     /**
diff --git a/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java 
b/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java
index 898ecda..fc00363 100644
--- a/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java
+++ b/src/main/java/org/apache/sling/i18n/RequestLocaleResolver.java
@@ -33,7 +33,9 @@ import java.util.Locale;
  * <p>
  * Only a single <code>RequestLocaleResolver</code> service is currently used.
  * @since 2.2
+ * @deprecated use {@link JakartaRequestLocaleResolver} instead
  */
+@Deprecated(since = "2.3.0")
 public interface RequestLocaleResolver {
 
     /**
diff --git a/src/main/java/org/apache/sling/i18n/impl/I18NFilter.java 
b/src/main/java/org/apache/sling/i18n/impl/I18NFilter.java
index 7e30d98..fb445c1 100644
--- a/src/main/java/org/apache/sling/i18n/impl/I18NFilter.java
+++ b/src/main/java/org/apache/sling/i18n/impl/I18NFilter.java
@@ -18,32 +18,31 @@
  */
 package org.apache.sling.i18n.impl;
 
-import javax.servlet.Filter;
-import javax.servlet.FilterChain;
-import javax.servlet.FilterConfig;
-import javax.servlet.ServletException;
-import javax.servlet.ServletRequest;
-import javax.servlet.ServletResponse;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletRequestWrapper;
-
 import java.io.IOException;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.MissingResourceException;
+import java.util.Objects;
 import java.util.ResourceBundle;
 import java.util.TreeMap;
 
-import org.apache.sling.api.SlingHttpServletRequest;
-import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
+import jakarta.servlet.Filter;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.wrappers.SlingJakartaHttpServletRequestWrapper;
 import org.apache.sling.commons.osgi.Order;
 import org.apache.sling.commons.osgi.ServiceUtil;
-import org.apache.sling.i18n.DefaultLocaleResolver;
-import org.apache.sling.i18n.LocaleResolver;
-import org.apache.sling.i18n.RequestLocaleResolver;
+import org.apache.sling.i18n.DefaultJakartaLocaleResolver;
+import org.apache.sling.i18n.JakartaRequestLocaleResolver;
 import org.apache.sling.i18n.ResourceBundleProvider;
 import org.osgi.framework.Constants;
 import org.osgi.service.component.annotations.Component;
@@ -81,11 +80,23 @@ public class I18NFilter implements Filter {
     /** Logger */
     private static final Logger LOG = 
LoggerFactory.getLogger(I18NFilter.class.getName());
 
-    private final DefaultLocaleResolver DEFAULT_LOCALE_RESOLVER = new 
DefaultLocaleResolver();
+    private final DefaultJakartaLocaleResolver defaultLocaleResolver = new 
DefaultJakartaLocaleResolver();
 
-    private volatile LocaleResolver localeResolver = DEFAULT_LOCALE_RESOLVER;
+    /**
+     * We can have potentially 3 different kinds of bound LocaleResolvers, so 
store
+     * the candidates here (ordered by preference) and then resolve which to 
use via
+     * the {@link #calculateBestLocaleResolver()} method
+     */
+    private final JakartaRequestLocaleResolver[] localeResolvers = new 
JakartaRequestLocaleResolver[] {
+        null, // for bound JakartaRequestLocaleResolver
+        null, // for deprecated bound RequestLocaleResolver
+        null // for deprecated bound LocaleResolver
+    };
 
-    private volatile RequestLocaleResolver requestLocaleResolver = 
DEFAULT_LOCALE_RESOLVER;
+    /**
+     * The current best locale resolver
+     */
+    private JakartaRequestLocaleResolver requestLocaleResolver = 
defaultLocaleResolver;
 
     private final Map<Object, ResourceBundleProvider> providers = new 
TreeMap<>();
 
@@ -93,34 +104,14 @@ public class I18NFilter implements Filter {
 
     private final ResourceBundleProvider combinedProvider = new 
CombinedBundleProvider();
 
-    /** Count the number init() has been called. */
-    private volatile int initCount;
-
-    /**
-     * @see javax.servlet.Filter#init(javax.servlet.FilterConfig)
-     */
-    @Override
-    public void init(FilterConfig filterConfig) {
-        synchronized (this) {
-            initCount++;
-        }
-    }
-
     /**
-     * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, 
javax.servlet.ServletResponse, javax.servlet.FilterChain)
+     * @see jakarta.servlet.Filter#doFilter(jakarta.servlet.ServletRequest, 
jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain)
      */
     @Override
     public void doFilter(ServletRequest request, final ServletResponse 
response, final FilterChain chain)
             throws IOException, ServletException {
-        final boolean runGlobal = this.initCount == 2;
-        if (request instanceof SlingHttpServletRequest) {
-            // check if we can use the simple version to wrap
-            if (!runGlobal || this.requestLocaleResolver == 
DEFAULT_LOCALE_RESOLVER) {
-                // wrap with our ResourceBundle provisioning
-                request = new I18NSlingHttpServletRequest(request, 
combinedProvider, localeResolver);
-            } else {
-                request = new BaseI18NSlingHttpServletRequest(request, 
combinedProvider);
-            }
+        if (request instanceof SlingJakartaHttpServletRequest) {
+            request = new I18NSlingJakartaHttpServletRequest(request, 
combinedProvider, requestLocaleResolver);
         } else {
             request = new I18NHttpServletRequest(request, combinedProvider, 
requestLocaleResolver);
         }
@@ -129,29 +120,71 @@ public class I18NFilter implements Filter {
         chain.doFilter(request, response);
     }
 
+    // ---------- SCR Integration 
----------------------------------------------
+
     /**
-     * @see javax.servlet.Filter#destroy()
+     * Given all the bound locale resolver candidates, resolve the preferred 
one.
+     * Use the non-deprecated candidate if we have one, or fallback to the 
least
+     * deprecated variant otherwise.
      */
-    @Override
-    public void destroy() {
-        synchronized (this) {
-            initCount--;
+    protected JakartaRequestLocaleResolver calculateBestLocaleResolver() {
+        return Arrays.stream(localeResolvers)
+                .filter(Objects::nonNull)
+                .findFirst()
+                .orElse(defaultLocaleResolver);
+    }
+
+    /**
+     * @deprecated use {@link 
#bindJakartaRequestLocaleResolver(JakartaRequestLocaleResolver)} instead
+     */
+    @Deprecated(since = "3.0.0")
+    @Reference(
+            cardinality = ReferenceCardinality.OPTIONAL,
+            policy = ReferencePolicy.DYNAMIC,
+            policyOption = ReferencePolicyOption.GREEDY)
+    protected void bindLocaleResolver(final 
org.apache.sling.i18n.LocaleResolver resolver) {
+        synchronized (localeResolvers) {
+            // wrap it with a JakartaRequestLocaleResolver impl
+            this.localeResolvers[2] = new LocaleResolverWrapper(resolver);
+            this.requestLocaleResolver = calculateBestLocaleResolver();
         }
     }
 
-    // ---------- SCR Integration 
----------------------------------------------
+    /**
+     * @deprecated use {@link 
#unbindJakartaRequestLocaleResolver(JakartaRequestLocaleResolver)} instead
+     */
+    @Deprecated(since = "3.0.0")
+    protected void unbindLocaleResolver(final 
org.apache.sling.i18n.LocaleResolver resolver) {
+        synchronized (localeResolvers) {
+            this.localeResolvers[2] = null;
+            this.requestLocaleResolver = calculateBestLocaleResolver();
+        }
+    }
 
+    /**
+     * @deprecated use {@link 
#bindJakartaRequestLocaleResolver(JakartaRequestLocaleResolver)} instead
+     */
+    @Deprecated(since = "3.0.0")
     @Reference(
             cardinality = ReferenceCardinality.OPTIONAL,
             policy = ReferencePolicy.DYNAMIC,
             policyOption = ReferencePolicyOption.GREEDY)
-    protected void bindLocaleResolver(final LocaleResolver resolver) {
-        this.localeResolver = resolver;
+    protected void bindRequestLocaleResolver(final 
org.apache.sling.i18n.RequestLocaleResolver resolver) {
+        synchronized (localeResolvers) {
+            // wrap it with a JakartaRequestLocaleResolver impl
+            this.localeResolvers[1] = new 
RequestLocaleResolverWrapper(resolver);
+            this.requestLocaleResolver = calculateBestLocaleResolver();
+        }
     }
 
-    protected void unbindLocaleResolver(final LocaleResolver resolver) {
-        if (this.localeResolver == resolver) {
-            this.localeResolver = DEFAULT_LOCALE_RESOLVER;
+    /**
+     * @deprecated use {@link 
#bindJakartaRequestLocaleResolver(JakartaRequestLocaleResolver)} instead
+     */
+    @Deprecated(since = "3.0.0")
+    protected void unbindRequestLocaleResolver(final 
org.apache.sling.i18n.RequestLocaleResolver resolver) {
+        synchronized (localeResolvers) {
+            this.localeResolvers[1] = null;
+            this.requestLocaleResolver = calculateBestLocaleResolver();
         }
     }
 
@@ -159,13 +192,17 @@ public class I18NFilter implements Filter {
             cardinality = ReferenceCardinality.OPTIONAL,
             policy = ReferencePolicy.DYNAMIC,
             policyOption = ReferencePolicyOption.GREEDY)
-    protected void bindRequestLocaleResolver(final RequestLocaleResolver 
resolver) {
-        this.requestLocaleResolver = resolver;
+    protected void bindJakartaRequestLocaleResolver(final 
JakartaRequestLocaleResolver resolver) {
+        synchronized (localeResolvers) {
+            this.localeResolvers[0] = resolver;
+            this.requestLocaleResolver = calculateBestLocaleResolver();
+        }
     }
 
-    protected void unbindRequestLocaleResolver(final RequestLocaleResolver 
resolver) {
-        if (this.requestLocaleResolver == resolver) {
-            this.requestLocaleResolver = DEFAULT_LOCALE_RESOLVER;
+    protected void unbindJakartaRequestLocaleResolver(final 
JakartaRequestLocaleResolver resolver) { // NOSONAR
+        synchronized (localeResolvers) {
+            this.localeResolvers[0] = null;
+            this.requestLocaleResolver = calculateBestLocaleResolver();
         }
     }
 
@@ -183,7 +220,7 @@ public class I18NFilter implements Filter {
     protected void unbindResourceBundleProvider(
             final ResourceBundleProvider provider, final Map<String, Object> 
props) {
         synchronized (this.providers) {
-            
this.providers.remove(ServiceUtil.getComparableForServiceRanking(props, 
Order.ASCENDING));
+            
this.providers.remove(ServiceUtil.getComparableForServiceRanking(props, 
Order.ASCENDING), provider);
             this.sortedProviders = this.providers.values().toArray(new 
ResourceBundleProvider[this.providers.size()]);
         }
     }
@@ -247,7 +284,7 @@ public class I18NFilter implements Filter {
 
         private final ResourceBundleProvider bundleProvider;
 
-        private final RequestLocaleResolver localeResolver;
+        private final JakartaRequestLocaleResolver localeResolver;
 
         private Locale locale;
 
@@ -258,7 +295,7 @@ public class I18NFilter implements Filter {
         I18NHttpServletRequest(
                 final ServletRequest delegatee,
                 final ResourceBundleProvider bundleProvider,
-                final RequestLocaleResolver localeResolver) {
+                final JakartaRequestLocaleResolver localeResolver) {
             super((HttpServletRequest) delegatee);
             this.bundleProvider = bundleProvider;
             this.localeResolver = localeResolver;
@@ -301,13 +338,22 @@ public class I18NFilter implements Filter {
         }
     }
 
-    private static class BaseI18NSlingHttpServletRequest extends 
SlingHttpServletRequestWrapper {
+    private static class I18NSlingJakartaHttpServletRequest extends 
SlingJakartaHttpServletRequestWrapper {
+
+        private final ResourceBundleProvider bundleProvider;
+        private final JakartaRequestLocaleResolver localeResolver;
 
-        protected final ResourceBundleProvider bundleProvider;
+        private Locale locale;
 
-        BaseI18NSlingHttpServletRequest(final ServletRequest delegatee, final 
ResourceBundleProvider bundleProvider) {
-            super((SlingHttpServletRequest) delegatee);
+        private List<Locale> localeList;
+
+        I18NSlingJakartaHttpServletRequest(
+                final ServletRequest delegatee,
+                final ResourceBundleProvider bundleProvider,
+                final JakartaRequestLocaleResolver localeResolver) {
+            super((SlingJakartaHttpServletRequest) delegatee);
             this.bundleProvider = bundleProvider;
+            this.localeResolver = localeResolver;
         }
 
         @Override
@@ -333,23 +379,6 @@ public class I18NFilter implements Filter {
 
             return super.getResourceBundle(baseName, locale);
         }
-    }
-
-    private static class I18NSlingHttpServletRequest extends 
BaseI18NSlingHttpServletRequest {
-
-        private final LocaleResolver localeResolver;
-
-        private Locale locale;
-
-        private List<Locale> localeList;
-
-        I18NSlingHttpServletRequest(
-                final ServletRequest delegatee,
-                final ResourceBundleProvider bundleProvider,
-                final LocaleResolver localeResolver) {
-            super(delegatee, bundleProvider);
-            this.localeResolver = localeResolver;
-        }
 
         @Override
         public Object getAttribute(final String name) {
diff --git 
a/src/main/java/org/apache/sling/i18n/impl/LocaleResolverWrapper.java 
b/src/main/java/org/apache/sling/i18n/impl/LocaleResolverWrapper.java
new file mode 100644
index 0000000..b0bc049
--- /dev/null
+++ b/src/main/java/org/apache/sling/i18n/impl/LocaleResolverWrapper.java
@@ -0,0 +1,61 @@
+/*
+ * 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.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.wrappers.JakartaToJavaxRequestWrapper;
+import org.apache.sling.i18n.JakartaRequestLocaleResolver;
+import org.apache.sling.i18n.LocaleResolver;
+
+/**
+ * Adapter to convert the a deprecated LocaleResolver object to
+ * JakartaRequestLocaleResolver
+ * @deprecated use a {@link JakartaRequestLocaleResolver} instead
+ */
+@Deprecated(since = "3.0.0")
+public class LocaleResolverWrapper implements JakartaRequestLocaleResolver {
+    private LocaleResolver wrapped;
+
+    public LocaleResolverWrapper(LocaleResolver wrapped) {
+        super();
+        this.wrapped = wrapped;
+    }
+
+    public LocaleResolver getWrapped() {
+        return wrapped;
+    }
+
+    @Override
+    public List<Locale> resolveLocale(HttpServletRequest request) {
+        List<Locale> list;
+        if (request instanceof SlingJakartaHttpServletRequest jsr) {
+            SlingHttpServletRequest javaxRequest = 
JakartaToJavaxRequestWrapper.toJavaxRequest(jsr);
+            list = wrapped.resolveLocale(javaxRequest);
+        } else {
+            list = Collections.emptyList();
+        }
+        return list;
+    }
+}
diff --git 
a/src/main/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapper.java 
b/src/main/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapper.java
new file mode 100644
index 0000000..97ced07
--- /dev/null
+++ b/src/main/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapper.java
@@ -0,0 +1,51 @@
+/*
+ * 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.impl;
+
+import java.util.List;
+import java.util.Locale;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.sling.api.wrappers.JakartaToJavaxRequestWrapper;
+import org.apache.sling.i18n.JakartaRequestLocaleResolver;
+import org.apache.sling.i18n.RequestLocaleResolver;
+
+/**
+ * Adapter to convert the a deprecated RequestLocaleResolver object to
+ * JakartaRequestLocaleResolver
+ * @deprecated use a {@link JakartaRequestLocaleResolver} instead
+ */
+@Deprecated(since = "3.0.0")
+public class RequestLocaleResolverWrapper implements 
JakartaRequestLocaleResolver {
+    private RequestLocaleResolver wrapped;
+
+    public RequestLocaleResolverWrapper(RequestLocaleResolver wrapped) {
+        this.wrapped = wrapped;
+    }
+
+    public RequestLocaleResolver getWrapped() {
+        return wrapped;
+    }
+
+    @Override
+    public List<Locale> resolveLocale(HttpServletRequest request) {
+        javax.servlet.http.HttpServletRequest javaxRequest = 
JakartaToJavaxRequestWrapper.toJavaxRequest(request);
+        return wrapped.resolveLocale(javaxRequest);
+    }
+}
diff --git a/src/main/java/org/apache/sling/i18n/package-info.java 
b/src/main/java/org/apache/sling/i18n/package-info.java
index 92c9c75..0c66eb6 100644
--- a/src/main/java/org/apache/sling/i18n/package-info.java
+++ b/src/main/java/org/apache/sling/i18n/package-info.java
@@ -17,5 +17,5 @@
  * under the License.
  */
 
[email protected]("2.2.1")
[email protected]("2.3.0")
 package org.apache.sling.i18n;
diff --git 
a/src/test/java/org/apache/sling/i18n/DefaultJakartaLocaleResolverTest.java 
b/src/test/java/org/apache/sling/i18n/DefaultJakartaLocaleResolverTest.java
new file mode 100644
index 0000000..20f602e
--- /dev/null
+++ b/src/test/java/org/apache/sling/i18n/DefaultJakartaLocaleResolverTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class DefaultJakartaLocaleResolverTest {
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.DefaultJakartaLocaleResolver#resolveLocale(jakarta.servlet.http.HttpServletRequest)}.
+     */
+    @Test
+    public void testResolveLocale() {
+        HttpServletRequest jakartaRequest = 
Mockito.mock(HttpServletRequest.class);
+        
Mockito.when(jakartaRequest.getLocales()).thenReturn(Collections.emptyEnumeration());
+        DefaultJakartaLocaleResolver resolver = new 
DefaultJakartaLocaleResolver();
+        List<Locale> locales = resolver.resolveLocale(jakartaRequest);
+        assertNotNull(locales);
+        assertTrue(locales.isEmpty());
+
+        Mockito.when(jakartaRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+        locales = resolver.resolveLocale(jakartaRequest);
+        assertNotNull(locales);
+        assertEquals(2, locales.size());
+        assertTrue(locales.contains(Locale.CANADA));
+        assertTrue(locales.contains(Locale.ENGLISH));
+    }
+}
diff --git a/src/test/java/org/apache/sling/i18n/DefaultLocaleResolverTest.java 
b/src/test/java/org/apache/sling/i18n/DefaultLocaleResolverTest.java
new file mode 100644
index 0000000..0c014d1
--- /dev/null
+++ b/src/test/java/org/apache/sling/i18n/DefaultLocaleResolverTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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;
+
+import javax.servlet.http.HttpServletRequest;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.sling.api.SlingHttpServletRequest;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @deprecated use {@link DefaultJakartaLocaleResolverTest} instead
+ */
+@Deprecated(since = "3.0.0")
+public class DefaultLocaleResolverTest {
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.DefaultLocaleResolver#resolveLocale(org.apache.sling.api.SlingHttpServletRequest)}.
+     */
+    @Test
+    public void testResolveLocaleSlingHttpServletRequest() {
+        SlingHttpServletRequest slingHttpRequest = 
Mockito.mock(SlingHttpServletRequest.class);
+        
Mockito.when(slingHttpRequest.getLocales()).thenReturn(Collections.emptyEnumeration());
+        DefaultLocaleResolver resolver = new DefaultLocaleResolver();
+        List<Locale> locales = resolver.resolveLocale(slingHttpRequest);
+        assertNotNull(locales);
+        assertTrue(locales.isEmpty());
+
+        Mockito.when(slingHttpRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+        locales = resolver.resolveLocale(slingHttpRequest);
+        assertNotNull(locales);
+        assertEquals(2, locales.size());
+        assertTrue(locales.contains(Locale.CANADA));
+        assertTrue(locales.contains(Locale.ENGLISH));
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.DefaultLocaleResolver#resolveLocale(javax.servlet.http.HttpServletRequest)}.
+     */
+    @Test
+    public void testResolveLocaleHttpServletRequest() {
+        HttpServletRequest javaxRequest = 
Mockito.mock(HttpServletRequest.class);
+        
Mockito.when(javaxRequest.getLocales()).thenReturn(Collections.emptyEnumeration());
+        DefaultLocaleResolver resolver = new DefaultLocaleResolver();
+        List<Locale> locales = resolver.resolveLocale(javaxRequest);
+        assertNotNull(locales);
+        assertTrue(locales.isEmpty());
+
+        Mockito.when(javaxRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+        locales = resolver.resolveLocale(javaxRequest);
+        assertNotNull(locales);
+        assertEquals(2, locales.size());
+        assertTrue(locales.contains(Locale.CANADA));
+        assertTrue(locales.contains(Locale.ENGLISH));
+    }
+}
diff --git a/src/test/java/org/apache/sling/i18n/impl/I18NFilterTest.java 
b/src/test/java/org/apache/sling/i18n/impl/I18NFilterTest.java
new file mode 100644
index 0000000..a588b5f
--- /dev/null
+++ b/src/test/java/org/apache/sling/i18n/impl/I18NFilterTest.java
@@ -0,0 +1,241 @@
+/*
+ * 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.impl;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletRequestWrapper;
+import jakarta.servlet.http.HttpServletResponse;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletResponse;
+import org.apache.sling.api.wrappers.SlingJakartaHttpServletRequestWrapper;
+import org.apache.sling.i18n.DefaultJakartaLocaleResolver;
+import org.apache.sling.i18n.JakartaRequestLocaleResolver;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+
+/**
+ *
+ */
+public class I18NFilterTest {
+
+    private I18NFilter filter = new I18NFilter();
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#doFilter(jakarta.servlet.ServletRequest, 
jakarta.servlet.ServletResponse, jakarta.servlet.FilterChain)}.
+     */
+    @Test
+    public void testDoFilterWithSlingJakartaHttpServletRequest() throws 
IOException, ServletException {
+        SlingJakartaHttpServletRequest request = 
Mockito.mock(SlingJakartaHttpServletRequest.class);
+        SlingJakartaHttpServletResponse response = 
Mockito.mock(SlingJakartaHttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+        AtomicReference<ServletRequest> reqHolder = new AtomicReference<>();
+        Mockito.doAnswer(invocation -> {
+                    ServletRequest req = invocation.getArgument(0, 
ServletRequest.class);
+                    reqHolder.set(req);
+                    return null;
+                })
+                .when(chain)
+                .doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+        filter.doFilter(request, response, chain);
+        ServletRequest invokedRequest = reqHolder.get();
+        assertTrue(invokedRequest instanceof 
SlingJakartaHttpServletRequestWrapper);
+        assertSame(request, ((SlingJakartaHttpServletRequestWrapper) 
invokedRequest).getRequest());
+    }
+
+    @Test
+    public void testDoFilterWithJakartaHttpServletRequest() throws 
IOException, ServletException {
+        HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
+        HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
+        FilterChain chain = Mockito.mock(FilterChain.class);
+        AtomicReference<ServletRequest> reqHolder = new AtomicReference<>();
+        Mockito.doAnswer(invocation -> {
+                    ServletRequest req = invocation.getArgument(0, 
ServletRequest.class);
+                    reqHolder.set(req);
+                    return null;
+                })
+                .when(chain)
+                .doFilter(any(ServletRequest.class), 
any(ServletResponse.class));
+        filter.doFilter(request, response, chain);
+        ServletRequest invokedRequest = reqHolder.get();
+        assertTrue(invokedRequest instanceof HttpServletRequestWrapper);
+        assertSame(request, ((HttpServletRequestWrapper) 
invokedRequest).getRequest());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#bindLocaleResolver(org.apache.sling.i18n.LocaleResolver)}.
+     * @deprecated use {@link #testBindJakartaRequestLocaleResolver()} instead
+     */
+    @Deprecated(since = "3.0.0")
+    @Test
+    public void testBindLocaleResolver() {
+        org.apache.sling.i18n.LocaleResolver localeResolver = new 
org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindLocaleResolver(localeResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof LocaleResolverWrapper);
+        assertSame(localeResolver, ((LocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#unbindLocaleResolver(org.apache.sling.i18n.LocaleResolver)}.
+     * @deprecated use {@link #testUnbindJakartaRequestLocaleResolver()} 
instead
+     */
+    @Deprecated(since = "3.0.0")
+    @Test
+    public void testUnbindLocaleResolver() {
+        org.apache.sling.i18n.LocaleResolver localeResolver = new 
org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindLocaleResolver(localeResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof LocaleResolverWrapper);
+        assertSame(localeResolver, ((LocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        filter.unbindLocaleResolver(localeResolver);
+        // should be back to the default locale resolver
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof DefaultJakartaLocaleResolver);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#bindRequestLocaleResolver(org.apache.sling.i18n.RequestLocaleResolver)}.
+     * @deprecated use {@link #testBindJakartaRequestLocaleResolver()} instead
+     */
+    @Deprecated(since = "3.0.0")
+    @Test
+    public void testBindRequestLocaleResolver() {
+        org.apache.sling.i18n.RequestLocaleResolver requestLocaleResolver =
+                new org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindRequestLocaleResolver(requestLocaleResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof RequestLocaleResolverWrapper);
+        assertSame(requestLocaleResolver, ((RequestLocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#unbindRequestLocaleResolver(org.apache.sling.i18n.RequestLocaleResolver)}.
+     * @deprecated use {@link #testUnbindJakartaRequestLocaleResolver()} 
instead
+     */
+    @Deprecated(since = "3.0.0")
+    @Test
+    public void testUnbindRequestLocaleResolver() {
+        org.apache.sling.i18n.RequestLocaleResolver requestLocaleResolver =
+                new org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindRequestLocaleResolver(requestLocaleResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof RequestLocaleResolverWrapper);
+        assertSame(requestLocaleResolver, ((RequestLocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        filter.unbindRequestLocaleResolver(requestLocaleResolver);
+        // should be back to the default locale resolver
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof DefaultJakartaLocaleResolver);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#bindJakartaRequestLocaleResolver(org.apache.sling.i18n.JakartaRequestLocaleResolver)}.
+     */
+    @Test
+    public void testBindJakartaRequestLocaleResolver() {
+        JakartaRequestLocaleResolver jakartaRequestLocaleResolver = new 
DefaultJakartaLocaleResolver();
+        filter.bindJakartaRequestLocaleResolver(jakartaRequestLocaleResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertSame(jakartaRequestLocaleResolver, bestLocaleResolver);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#unbindJakartaRequestLocaleResolver(org.apache.sling.i18n.JakartaRequestLocaleResolver)}.
+     */
+    @Test
+    public void testUnbindJakartaRequestLocaleResolver() {
+        JakartaRequestLocaleResolver jakartaRequestLocaleResolver = new 
DefaultJakartaLocaleResolver();
+        filter.bindJakartaRequestLocaleResolver(jakartaRequestLocaleResolver);
+
+        // the bound object should be now be the current resolver
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertSame(jakartaRequestLocaleResolver, bestLocaleResolver);
+
+        
filter.unbindJakartaRequestLocaleResolver(jakartaRequestLocaleResolver);
+        // should be back to the default locale resolver
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertNotSame(jakartaRequestLocaleResolver, bestLocaleResolver);
+        assertTrue(bestLocaleResolver instanceof DefaultJakartaLocaleResolver);
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.I18NFilter#calculateBestLocaleResolver()}.
+     */
+    @SuppressWarnings("deprecation")
+    @Test
+    public void testCalculateBestLocaleResolver() {
+        JakartaRequestLocaleResolver defaultLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertNotNull(defaultLocaleResolver);
+
+        org.apache.sling.i18n.LocaleResolver localeResolver = new 
org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindLocaleResolver(localeResolver);
+        JakartaRequestLocaleResolver bestLocaleResolver = 
filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof LocaleResolverWrapper);
+        assertSame(localeResolver, ((LocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        org.apache.sling.i18n.RequestLocaleResolver requestLocaleResolver =
+                new org.apache.sling.i18n.DefaultLocaleResolver();
+        filter.bindRequestLocaleResolver(requestLocaleResolver);
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof RequestLocaleResolverWrapper);
+        assertSame(requestLocaleResolver, ((RequestLocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        JakartaRequestLocaleResolver jakartaRequestLocaleResolver = new 
DefaultJakartaLocaleResolver();
+        filter.bindJakartaRequestLocaleResolver(jakartaRequestLocaleResolver);
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertSame(jakartaRequestLocaleResolver, bestLocaleResolver);
+
+        
filter.unbindJakartaRequestLocaleResolver(jakartaRequestLocaleResolver);
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof RequestLocaleResolverWrapper);
+        assertSame(requestLocaleResolver, ((RequestLocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        filter.unbindRequestLocaleResolver(requestLocaleResolver);
+        bestLocaleResolver = filter.calculateBestLocaleResolver();
+        assertTrue(bestLocaleResolver instanceof LocaleResolverWrapper);
+        assertSame(localeResolver, ((LocaleResolverWrapper) 
bestLocaleResolver).getWrapped());
+
+        filter.unbindLocaleResolver(localeResolver);
+        assertSame(defaultLocaleResolver, 
filter.calculateBestLocaleResolver());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/i18n/impl/LocaleResolverWrapperTest.java 
b/src/test/java/org/apache/sling/i18n/impl/LocaleResolverWrapperTest.java
new file mode 100644
index 0000000..6cd47d0
--- /dev/null
+++ b/src/test/java/org/apache/sling/i18n/impl/LocaleResolverWrapperTest.java
@@ -0,0 +1,85 @@
+/*
+ * 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.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.sling.api.SlingJakartaHttpServletRequest;
+import org.apache.sling.i18n.DefaultLocaleResolver;
+import org.apache.sling.i18n.LocaleResolver;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+@Deprecated(since = "3.0.0")
+public class LocaleResolverWrapperTest {
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.LocaleResolverWrapper#getWrapped()}.
+     */
+    @Test
+    public void testGetWrapped() {
+        LocaleResolver localeResolver = Mockito.mock(LocaleResolver.class);
+        LocaleResolverWrapper wrapper = new 
LocaleResolverWrapper(localeResolver);
+        assertSame(localeResolver, wrapper.getWrapped());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.LocaleResolverWrapper#resolveLocale(jakarta.servlet.http.HttpServletRequest)}.
+     */
+    @Test
+    public void testResolveLocaleWithSlingJakartaRequest() {
+        LocaleResolver localeResolver = new DefaultLocaleResolver();
+        LocaleResolverWrapper wrapper = new 
LocaleResolverWrapper(localeResolver);
+
+        SlingJakartaHttpServletRequest slingJakartaRequest = 
Mockito.mock(SlingJakartaHttpServletRequest.class);
+        Mockito.when(slingJakartaRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+
+        List<Locale> locales = wrapper.resolveLocale(slingJakartaRequest);
+        assertNotNull(locales);
+        assertEquals(2, locales.size());
+        assertTrue(locales.contains(Locale.CANADA));
+        assertTrue(locales.contains(Locale.ENGLISH));
+    }
+
+    @Test
+    public void testResolveLocaleWithJakartaRequest() {
+        LocaleResolver localeResolver = new DefaultLocaleResolver();
+        LocaleResolverWrapper wrapper = new 
LocaleResolverWrapper(localeResolver);
+
+        HttpServletRequest jakartaRequest = 
Mockito.mock(HttpServletRequest.class);
+        Mockito.when(jakartaRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+
+        List<Locale> locales = wrapper.resolveLocale(jakartaRequest);
+        assertNotNull(locales);
+        assertTrue(locales.isEmpty());
+    }
+}
diff --git 
a/src/test/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapperTest.java
 
b/src/test/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapperTest.java
new file mode 100644
index 0000000..b16f42f
--- /dev/null
+++ 
b/src/test/java/org/apache/sling/i18n/impl/RequestLocaleResolverWrapperTest.java
@@ -0,0 +1,70 @@
+/*
+ * 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.impl;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.apache.sling.i18n.DefaultLocaleResolver;
+import org.apache.sling.i18n.RequestLocaleResolver;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+@Deprecated(since = "3.0.0")
+public class RequestLocaleResolverWrapperTest {
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.RequestLocaleResolverWrapper#getWrapped()}.
+     */
+    @Test
+    public void testGetWrapped() {
+        RequestLocaleResolver localeResolver = 
Mockito.mock(RequestLocaleResolver.class);
+        RequestLocaleResolverWrapper wrapper = new 
RequestLocaleResolverWrapper(localeResolver);
+        assertSame(localeResolver, wrapper.getWrapped());
+    }
+
+    /**
+     * Test method for {@link 
org.apache.sling.i18n.impl.RequestLocaleResolverWrapper#resolveLocale(jakarta.servlet.http.HttpServletRequest)}.
+     */
+    @Test
+    public void testResolveLocale() {
+        RequestLocaleResolver localeResolver = new DefaultLocaleResolver();
+        RequestLocaleResolverWrapper wrapper = new 
RequestLocaleResolverWrapper(localeResolver);
+
+        HttpServletRequest javaxRequest = 
Mockito.mock(HttpServletRequest.class);
+        Mockito.when(javaxRequest.getLocales())
+                .thenReturn(Collections.enumeration(List.of(Locale.CANADA, 
Locale.ENGLISH)));
+
+        List<Locale> locales = wrapper.resolveLocale(javaxRequest);
+        assertNotNull(locales);
+        assertEquals(2, locales.size());
+        assertTrue(locales.contains(Locale.CANADA));
+        assertTrue(locales.contains(Locale.ENGLISH));
+    }
+}
diff --git a/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java 
b/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java
index 951f146..e4725b3 100644
--- a/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java
+++ b/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java
@@ -24,10 +24,14 @@ import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.options.ModifiableCompositeOption;
 import org.ops4j.pax.exam.options.extra.VMOption;
 
+import static org.apache.sling.testing.paxexam.SlingOptions.paxLoggingApi;
 import static 
org.apache.sling.testing.paxexam.SlingOptions.slingQuickstartOakTar;
+import static org.apache.sling.testing.paxexam.SlingOptions.versionResolver;
 import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.junitBundles;
+import static org.ops4j.pax.exam.CoreOptions.mavenBundle;
 import static org.ops4j.pax.exam.CoreOptions.options;
+import static org.ops4j.pax.exam.CoreOptions.systemProperty;
 import static org.ops4j.pax.exam.CoreOptions.vmOption;
 import static 
org.ops4j.pax.exam.cm.ConfigurationAdminOptions.factoryConfiguration;
 import static org.ops4j.pax.exam.cm.ConfigurationAdminOptions.newConfiguration;
@@ -36,9 +40,23 @@ public abstract class I18nTestSupport extends TestSupport {
 
     @Configuration
     public Option[] configuration() {
+        // SLING-12312 - newer version of sling.api and dependencies
+        //   may remove at a later date if the superclass includes these 
versions or later
+        versionResolver.setVersionFromProject("org.apache.sling", 
"org.apache.sling.api");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.engine", "3.0.0");
+        versionResolver.setVersion("org.apache.felix", 
"org.apache.felix.http.servlet-api", "6.1.0");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.resourceresolver", "2.0.0");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.auth.core", "2.0.0");
+        versionResolver.setVersion("commons-fileupload", "commons-fileupload", 
"1.6.0");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.scripting.spi", "2.0.0");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.scripting.core", "3.0.0");
+        versionResolver.setVersion("org.apache.sling", 
"org.apache.sling.servlets.resolver", "3.0.0");
+
         return options(
                 baseConfiguration(),
                 quickstart(),
+                paxLoggingApi(), // newer version to provide the 2.x version 
of slf4j
+                
systemProperty("org.ops4j.pax.logging.DefaultServiceLog.level").value("INFO"),
                 // Sling I18N
                 testBundle("bundle.filename"),
                 
factoryConfiguration("org.apache.sling.jcr.repoinit.RepositoryInitializer")
@@ -53,6 +71,16 @@ public abstract class I18nTestSupport extends TestSupport {
                 
newConfiguration("org.apache.sling.jcr.base.internal.LoginAdminWhitelist")
                         .put("whitelist.bundles.regexp", "PAXEXAM-PROBE-.*")
                         .asOption(),
+                // SLING-12312 - begin extra bundles for sling api 3.x
+                mavenBundle()
+                        .groupId("org.apache.felix")
+                        .artifactId("org.apache.felix.http.wrappers")
+                        .versionAsInProject(),
+                mavenBundle()
+                        .groupId("org.apache.sling")
+                        .artifactId("org.apache.sling.commons.johnzon")
+                        .version("2.0.0"),
+                // end extra bundles for sling api 3.x
                 junitBundles(),
                 optionalRemoteDebug(),
                 optionalJacocoCommand());
diff --git 
a/src/test/java/org/apache/sling/i18n/it/ResourceBundleLocatorIT.java 
b/src/test/java/org/apache/sling/i18n/it/ResourceBundleLocatorIT.java
index ad00168..d3ba0c9 100644
--- a/src/test/java/org/apache/sling/i18n/it/ResourceBundleLocatorIT.java
+++ b/src/test/java/org/apache/sling/i18n/it/ResourceBundleLocatorIT.java
@@ -44,8 +44,8 @@ import org.ops4j.pax.exam.Option;
 import org.ops4j.pax.exam.junit.PaxExam;
 import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
 import org.ops4j.pax.exam.spi.reactors.PerClass;
-import org.ops4j.pax.tinybundles.core.TinyBundle;
-import org.ops4j.pax.tinybundles.core.TinyBundles;
+import org.ops4j.pax.tinybundles.TinyBundle;
+import org.ops4j.pax.tinybundles.TinyBundles;
 import org.osgi.framework.Constants;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -55,7 +55,6 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.ops4j.pax.exam.CoreOptions.composite;
 import static org.ops4j.pax.exam.CoreOptions.streamBundle;
-import static org.ops4j.pax.tinybundles.core.TinyBundles.withBnd;
 
 /**
  * Tests for SLING-10135 for locating resource bundle resources
@@ -149,10 +148,10 @@ public class ResourceBundleLocatorIT extends 
I18nTestSupport {
                 String value = IOUtils.toString(is, StandardCharsets.UTF_8);
                 value = String.format(value, args);
                 try (final InputStream valueStream = new 
ByteArrayInputStream(value.getBytes())) {
-                    bundle.add(pathInBundle, valueStream);
+                    bundle.addResource(pathInBundle, valueStream);
                 }
             } else {
-                bundle.add(pathInBundle, is);
+                bundle.addResource(pathInBundle, is);
             }
         }
     }
@@ -167,32 +166,33 @@ public class ResourceBundleLocatorIT extends 
I18nTestSupport {
             String basename)
             throws IOException {
         final TinyBundle bundle = TinyBundles.bundle();
-        bundle.set(Constants.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
-        bundle.set(
+        bundle.setHeader(Constants.BUNDLE_SYMBOLICNAME, bundleSymbolicName);
+        bundle.setHeader(
                 Constants.REQUIRE_CAPABILITY,
                 
"osgi.extender;filter:=\"(&(osgi.extender=org.apache.sling.i18n.resourcebundle.locator.registrar)(version<=1.0.0)(!(version>=2.0.0)))\"");
         if (traverseDepth <= 0) {
             if (traversePath == null) {
-                bundle.set(Constants.PROVIDE_CAPABILITY, 
"org.apache.sling.i18n.resourcebundle.locator");
+                bundle.setHeader(Constants.PROVIDE_CAPABILITY, 
"org.apache.sling.i18n.resourcebundle.locator");
             } else {
-                bundle.set(
+                bundle.setHeader(
                         Constants.PROVIDE_CAPABILITY,
                         
String.format("org.apache.sling.i18n.resourcebundle.locator;paths=\"%s\"", 
traversePath));
             }
         } else {
             if (traversePath == null) {
-                bundle.set(
+                bundle.setHeader(
                         Constants.PROVIDE_CAPABILITY,
                         
String.format("org.apache.sling.i18n.resourcebundle.locator;depth=%d", 
traverseDepth));
             } else {
-                bundle.set(
+                bundle.setHeader(
                         Constants.PROVIDE_CAPABILITY,
                         String.format(
                                 
"org.apache.sling.i18n.resourcebundle.locator;paths=\"%s\";depth=%d",
                                 traversePath, traverseDepth));
             }
         }
-        bundle.set("Sling-Bundle-Resources", 
String.format("%s;path:=%s;propsJSON:=props", resourcePath, pathInBundle));
+        bundle.setHeader(
+                "Sling-Bundle-Resources", 
String.format("%s;path:=%s;propsJSON:=props", resourcePath, pathInBundle));
 
         for (final Map.Entry<String, String> entry : content.entries()) {
             String entryPathInBundle = entry.getKey();
@@ -205,7 +205,7 @@ public class ResourceBundleLocatorIT extends 
I18nTestSupport {
                 addContent(bundle, entryPathInBundle, entryResourcePath);
             }
         }
-        return streamBundle(bundle.build(withBnd())).start();
+        return streamBundle(bundle.build(TinyBundles.bndBuilder())).start();
     }
 
     @Before
diff --git a/src/test/resources/exam.properties 
b/src/test/resources/exam.properties
new file mode 100644
index 0000000..486cdc6
--- /dev/null
+++ b/src/test/resources/exam.properties
@@ -0,0 +1,21 @@
+#
+# 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.
+#
+
+# Disable the default bundles provisioned under pax logging
+pax.exam.logging=none

Reply via email to