This is an automated email from the ASF dual-hosted git repository. joerghoh pushed a commit to branch SLING-13171-2 in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-i18n.git
commit 2a8638c5bde0b0de6e44a5fdcb275abc243d8e09 Author: Joerg Hoh <[email protected]> AuthorDate: Sat Apr 25 18:00:18 2026 +0200 SLING-13171 use Apache Johnzon instead of the JCR JsonParser --- bnd.bnd | 3 - pom.xml | 20 ++++-- .../apache/sling/i18n/impl/JcrResourceBundle.java | 80 +++++++--------------- .../sling/i18n/impl/JcrResourceBundleTest.java | 41 +++++++++++ .../org/apache/sling/i18n/it/I18nTestSupport.java | 8 +++ 5 files changed, 88 insertions(+), 64 deletions(-) diff --git a/bnd.bnd b/bnd.bnd index 221feb5..71e3d80 100644 --- a/bnd.bnd +++ b/bnd.bnd @@ -10,9 +10,6 @@ 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 883fccf..897cb23 100644 --- a/pom.xml +++ b/pom.xml @@ -43,8 +43,8 @@ <properties> <project.build.outputTimestamp>2025-08-20T22:13:35Z</project.build.outputTimestamp> + <johnzon.version>1.2.22</johnzon.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>17</sling.java.version> <slf4j.version>2.0.17</slf4j.version> @@ -111,18 +111,24 @@ <artifactId>org.osgi.service.metatype.annotations</artifactId> <scope>provided</scope> </dependency> - <dependency> - <groupId>org.apache.jackrabbit</groupId> - <artifactId>jackrabbit-jcr-commons</artifactId> - <version>${jackrabbit.version}</version> - <scope>provided</scope> - </dependency> <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.api</artifactId> <version>3.0.0</version> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-json_1.1_spec</artifactId> + <version>1.5</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.johnzon</groupId> + <artifactId>johnzon-core</artifactId> + <version>${johnzon.version}</version> + <scope>provided</scope> + </dependency> <dependency> <groupId>org.apache.sling</groupId> <artifactId>org.apache.sling.commons.osgi</artifactId> 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 ad9585c..49bab45 100644 --- a/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java +++ b/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java @@ -18,8 +18,15 @@ */ package org.apache.sling.i18n.impl; +import javax.json.spi.JsonProvider; +import javax.json.stream.JsonParser; +import javax.json.stream.JsonParser.Event; + import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -34,8 +41,6 @@ import java.util.Map; import java.util.ResourceBundle; import java.util.Set; -import org.apache.jackrabbit.commons.json.JsonHandler; -import org.apache.jackrabbit.commons.json.JsonParser; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceMetadata; import org.apache.sling.api.resource.ResourceResolver; @@ -245,62 +250,29 @@ public class JcrResourceBundle extends ResourceBundle { private void loadJsonDictionary(Resource resource, final Map<String, Object> targetDictionary) { log.info("Loading json dictionary: {}", resource.getPath()); - // use streaming parser (we don't need the dict in memory twice) - JsonParser parser = new JsonParser(new JsonHandler() { - - private String key; - - @Override - public void key(String key) throws IOException { - this.key = key; - } - - @Override - public void value(String value) throws IOException { - targetDictionary.put(key, value); - } - - @Override - public void object() throws IOException {} - - @Override - public void endObject() throws IOException {} - - @Override - public void array() throws IOException {} - - @Override - public void endArray() throws IOException {} - - @Override - public void value(boolean value) throws IOException {} - - @Override - public void value(long value) throws IOException {} - - @Override - public void value(double value) throws IOException {} - }); - final InputStream stream = resource.adaptTo(InputStream.class); if (stream != null) { - String encoding = "utf-8"; - final ResourceMetadata metadata = resource.getResourceMetadata(); - if (metadata.getCharacterEncoding() != null) { - encoding = metadata.getCharacterEncoding(); - } - - try { - - parser.parse(stream, encoding); + try (InputStream input = stream) { + Charset charset = StandardCharsets.UTF_8; + final ResourceMetadata metadata = resource.getResourceMetadata(); + if (metadata != null && metadata.getCharacterEncoding() != null) { + charset = Charset.forName(metadata.getCharacterEncoding()); + } - } catch (IOException e) { - log.warn("Could not parse i18n json dictionary {}: {}", resource.getPath(), e.getMessage()); - } finally { - try { - stream.close(); - } catch (IOException ignore) { + try (InputStreamReader reader = new InputStreamReader(input, charset); + JsonParser parser = JsonProvider.provider().createParser(reader)) { + String key = null; + while (parser.hasNext()) { + final Event event = parser.next(); + if (event == Event.KEY_NAME) { + key = parser.getString(); + } else if (event == Event.VALUE_STRING && key != null) { + targetDictionary.put(key, parser.getString()); + } + } } + } catch (IOException | RuntimeException e) { + log.warn("Could not parse i18n json dictionary {}: {}", resource.getPath(), e.getMessage()); } } else { log.warn("Not a json file: {}", resource.getPath()); diff --git a/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java b/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java index 38d9ba5..99b5929 100644 --- a/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java +++ b/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java @@ -28,7 +28,9 @@ import java.io.ByteArrayInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.lang.reflect.Method; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.LinkedHashMap; @@ -36,6 +38,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.testing.mock.jcr.MockJcr; import org.apache.sling.testing.mock.jcr.MockQueryResult; @@ -44,6 +48,7 @@ import org.apache.sling.testing.mock.sling.junit.SlingContext; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.Mockito; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -447,4 +452,40 @@ public class JcrResourceBundleTest { } assertEquals(MESSAGES_DE.size(), counter); } + + @Test + public void test_json_dictionary_with_invalid_encoding_is_ignored() throws Exception { + Map<String, Object> targetDictionary = new LinkedHashMap<>(); + + invokeLoadJsonDictionary(mockJsonResource("{\"key\":\"value\"}", "does-not-exist"), targetDictionary); + + assertTrue("dictionary should stay empty for invalid encoding metadata", targetDictionary.isEmpty()); + } + + @Test + public void test_json_dictionary_with_invalid_json_is_ignored() throws Exception { + Map<String, Object> targetDictionary = new LinkedHashMap<>(); + + invokeLoadJsonDictionary(mockJsonResource("{\"key\":", StandardCharsets.UTF_8.name()), targetDictionary); + + assertTrue("dictionary should stay empty for malformed JSON", targetDictionary.isEmpty()); + } + + private void invokeLoadJsonDictionary(Resource resource, Map<String, Object> targetDictionary) throws Exception { + JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"), null, resolver, null, new PathFilter()); + Method method = JcrResourceBundle.class.getDeclaredMethod("loadJsonDictionary", Resource.class, Map.class); + method.setAccessible(true); + method.invoke(bundle, resource, targetDictionary); + } + + private Resource mockJsonResource(String json, String encoding) { + Resource resource = Mockito.mock(Resource.class); + ResourceMetadata metadata = Mockito.mock(ResourceMetadata.class); + Mockito.when(resource.getPath()).thenReturn("/apps/i18n/de.json"); + Mockito.when(resource.getResourceMetadata()).thenReturn(metadata); + Mockito.when(metadata.getCharacterEncoding()).thenReturn(encoding); + Mockito.when(resource.adaptTo(InputStream.class)) + .thenReturn(new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8))); + return resource; + } } 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 e4725b3..387e108 100644 --- a/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java +++ b/src/test/java/org/apache/sling/i18n/it/I18nTestSupport.java @@ -76,6 +76,14 @@ public abstract class I18nTestSupport extends TestSupport { .groupId("org.apache.felix") .artifactId("org.apache.felix.http.wrappers") .versionAsInProject(), + mavenBundle() + .groupId("org.apache.geronimo.specs") + .artifactId("geronimo-json_1.1_spec") + .version("1.5"), + mavenBundle() + .groupId("org.apache.johnzon") + .artifactId("johnzon-core") + .versionAsInProject(), mavenBundle() .groupId("org.apache.sling") .artifactId("org.apache.sling.commons.johnzon")
