Author: bdelacretaz
Date: Wed Nov 4 23:53:42 2009
New Revision: 832918
URL: http://svn.apache.org/viewvc?rev=832918&view=rev
Log:
SLING-1131 - i18n: do not enforce a flat node hierarchy below mix:language.
Contributed by Alex Klimetschek, thanks!
Added:
sling/trunk/contrib/extensions/i18n/src/test/
sling/trunk/contrib/extensions/i18n/src/test/java/
sling/trunk/contrib/extensions/i18n/src/test/java/org/
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
(with props)
sling/trunk/contrib/extensions/i18n/src/test/resources/
sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties
(with props)
Modified:
sling/trunk/contrib/extensions/i18n/pom.xml
sling/trunk/contrib/extensions/i18n/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
Modified: sling/trunk/contrib/extensions/i18n/pom.xml
URL:
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/i18n/pom.xml?rev=832918&r1=832917&r2=832918&view=diff
==============================================================================
--- sling/trunk/contrib/extensions/i18n/pom.xml (original)
+++ sling/trunk/contrib/extensions/i18n/pom.xml Wed Nov 4 23:53:42 2009
@@ -113,6 +113,39 @@
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</dependency>
+
+ <!-- Testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sling</groupId>
+ <artifactId>org.apache.sling.commons.testing</artifactId>
+ <version>2.0.5-SNAPSHOT</version>
+ <scope>test</scope>
+ <exclusions>
+ <!-- slf4j simple implementation logs INFO + higher to stdout
(we don't want that behaviour) -->
+ <exclusion>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <!-- using log4j under slf4j to allow fine-grained logging config (see
src/test/resources/log4j.properties) -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <version>1.5.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.13</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
Modified:
sling/trunk/contrib/extensions/i18n/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
URL:
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/i18n/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java?rev=832918&r1=832917&r2=832918&view=diff
==============================================================================
---
sling/trunk/contrib/extensions/i18n/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
(original)
+++
sling/trunk/contrib/extensions/i18n/src/main/java/org/apache/sling/i18n/impl/JcrResourceBundle.java
Wed Nov 4 23:53:42 2009
@@ -29,10 +29,15 @@
import javax.jcr.query.Query;
+import org.apache.jackrabbit.util.Text;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class JcrResourceBundle extends ResourceBundle {
+
+ private static final Logger log =
LoggerFactory.getLogger(JcrResourceBundle.class);
private static final String JCR_PATH = "jcr:path";
@@ -46,21 +51,22 @@
* Search the tree below a mix:language node matching a given language...
*/
private static final String QUERY_BASE =
- "//element(*,mix:language)[...@jcr:language='%s'%s]//*";
+
"//element(*,mix:language)[...@jcr:language='%s'%s]//element(*,sling:Message)";
/**
* ... and find all nodes with a sling:message property set
* (typically with mixin sling:Message).
*/
private static final String QUERY_LOAD_FULLY = QUERY_BASE
- + "[@" + PROP_VALUE + "]/@" + PROP_VALUE;
+ + "[@" + PROP_VALUE + "]/(@" + PROP_KEY + "|@" + PROP_VALUE + ")";
/**
* ... or find a node with the message (sling:message property) for
- * a given key (sling:key property).
+ * a given key (sling:key property). Also find nodes without sling:key
+ * property and examine their node name.
*/
private static final String QUERY_LOAD_RESOURCE = QUERY_BASE + "[@"
- + PROP_KEY + "='%s']/@" + PROP_VALUE;
+ + PROP_KEY + "='%s' or not(@" + PROP_KEY + ")]/(@" + PROP_KEY + "|@" +
PROP_VALUE + ")";
private final ResourceResolver resourceResolver;
@@ -120,8 +126,12 @@
private void loadFully() {
if (!fullyLoaded) {
+ final String fullLoadQuery = getFullLoadQuery();
+ if (log.isDebugEnabled()) {
+ log.debug("Executing full load query {}", fullLoadQuery);
+ }
Iterator<Map<String, Object>> bundles =
resourceResolver.queryResources(
- getFullLoadQuery(), Query.XPATH);
+ fullLoadQuery, Query.XPATH);
String[] path = getSearchPath();
@@ -162,9 +172,12 @@
}
private Object loadResource(String key) {
- // query for the resource
- Iterator<Map<String, Object>> bundles =
resourceResolver.queryResources(
- getResourceQuery(key), Query.XPATH);
+ final String resourceQuery = getResourceQuery(key);
+ if (log.isDebugEnabled()) {
+ log.debug("Executing resource query {}", resourceQuery);
+ }
+ Iterator<Map<String, Object>> bundles = resourceResolver
+ .queryResources(resourceQuery, Query.XPATH);
if (bundles.hasNext()) {
String[] path = getSearchPath();
@@ -175,6 +188,13 @@
while (bundles.hasNext() && currentWeight > 0) {
Map<String, Object> resource = bundles.next();
String jcrPath = (String) resource.get(JCR_PATH);
+
+ // skip resources without sling:key and non-matching nodename
+ if (resource.get(PROP_KEY) == null) {
+ if (!key.equals(Text.getName(jcrPath))) {
+ continue;
+ }
+ }
for (int i = 0; i < currentWeight; i++) {
if (jcrPath.startsWith(path[i])) {
@@ -190,8 +210,10 @@
currentValue = resource;
}
}
-
- return currentValue.get(PROP_VALUE);
+
+ if (currentValue != null) {
+ return currentValue.get(PROP_VALUE);
+ }
}
return null;
Added:
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
URL:
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java?rev=832918&view=auto
==============================================================================
---
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
(added)
+++
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
Wed Nov 4 23:53:42 2009
@@ -0,0 +1,278 @@
+/*
+ * 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.Enumeration;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+
+import org.apache.sling.api.resource.ResourceResolver;
+import org.apache.sling.commons.testing.SlingTestHelper;
+import org.apache.sling.commons.testing.jcr.RepositoryTestBase;
+import org.apache.sling.commons.testing.jcr.RepositoryUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Tests the {...@link JcrResourceBundle} class.
+ */
+public class JcrResourceBundleTest extends RepositoryTestBase {
+
+ private static final Logger log =
LoggerFactory.getLogger(JcrResourceBundleTest.class);
+
+ protected ResourceResolver resolver;
+
+ public void setUp() throws Exception {
+ super.setUp();
+
+ cleanRepository();
+
+ SlingTestHelper.registerSlingNodeTypes(getSession());
+ RepositoryUtil.registerNodeType(getSession(), getClass()
+ .getResourceAsStream("/SLING-INF/nodetypes/jcrlanguage.cnd"));
+ RepositoryUtil.registerNodeType(getSession(), getClass()
+ .getResourceAsStream("/SLING-INF/nodetypes/message.cnd"));
+
+ resolver = SlingTestHelper.getResourceResolver(getRepository(),
+ getSession());
+
+ createTestContent();
+ }
+
+ public void cleanRepository() throws Exception {
+ NodeIterator nodes = getSession().getRootNode().getNodes();
+ while (nodes.hasNext()) {
+ Node node = nodes.nextNode();
+ if (!node.getDefinition().isProtected() &&
!node.getDefinition().isMandatory()) {
+ try {
+ node.remove();
+ } catch (RepositoryException e) {
+ log.error("Test clean repo: Cannot remove node: " +
node.getPath(), e);
+ }
+ }
+ }
+ getSession().save();
+ }
+
+ // ---------------------------------------------------------------< test
data helper >
+
+ /**
+ * Helper class for creating test data in a generic way.
+ */
+ public static class Message {
+ public String key;
+ public String message;
+ public boolean useNodeName;
+ public String path;
+
+ public Message(String path, String key, String message, boolean
useNodeName) {
+ this.path = path;
+ this.key = key;
+ this.message = message;
+ this.useNodeName = useNodeName;
+ }
+
+ private static int nodeNameCounter = 0;
+
+ public void add(Node languageNode) throws RepositoryException {
+ Node node = languageNode;
+ String[] pathElements = path.split("/");
+ for (String pathStep : pathElements) {
+ if (pathStep != null && pathStep.length() > 0) {
+ node = node.addNode(pathStep, "nt:folder");
+ }
+ }
+ if (useNodeName) {
+ node = node.addNode(key, "sling:MessageEntry");
+ } else {
+ node = node.addNode("node" + nodeNameCounter,
"sling:MessageEntry");
+ nodeNameCounter++;
+ node.setProperty("sling:key", key);
+ }
+ node.setProperty("sling:message", message);
+ }
+ }
+
+ // test data to add to the repository (use linked hash map for insertion
order)
+ public static final Map<String, Message> MESSAGES_DE = new
LinkedHashMap<String, Message>();
+ public static final Map<String, Message> MESSAGES_EN = new
LinkedHashMap<String, Message>();
+ public static final Map<String, Message> MESSAGES_DE_APPS = new
LinkedHashMap<String, Message>();
+ public static final Map<String, Message> MESSAGES_DE_BASENAME = new
LinkedHashMap<String, Message>();
+
+ public static void add(Map<String, Message> map, Message msg) {
+ map.put(msg.key, msg);
+ }
+
+ public static final Message PARENT_MSG = new Message("", "untranslated",
"means: not translated", false);
+
+ // create test data
+ static {
+ // 1. direct child node of language node, using sling:key
+ add(MESSAGES_DE, new Message("", "kitchen", "Kche", false));
+ // 2. direct child node of language node, using nodename
+ add(MESSAGES_DE, new Message("", "plate", "Teller", true));
+ // 3. nested node, using sling:key
+ add(MESSAGES_DE, new Message("f", "fork", "Gabel", false));
+ // 4. nested node, using nodename
+ add(MESSAGES_DE, new Message("s/p/o", "spoon", "Lffel", true));
+
+ // 5. not present in DE
+ add(MESSAGES_DE, PARENT_MSG);
+
+ // 6. same as 1.-4., but different translations for overwriting into
apps
+ for (Message msg : MESSAGES_DE.values()) {
+ add(MESSAGES_DE_APPS, new Message(msg.path, msg.key, "OTHER",
msg.useNodeName));
+ }
+
+ // 7. same as 1.-4., but different translations for different
sling:basename
+ for (Message msg : MESSAGES_DE.values()) {
+ add(MESSAGES_DE_BASENAME, new Message(msg.path, msg.key,
"BASENAME", msg.useNodeName));
+ }
+ }
+
+ public void createTestContent() throws Exception {
+ Node i18n = getSession().getRootNode().addNode("libs",
"nt:unstructured").addNode("i18n", "nt:unstructured");
+
+ // some DE content
+ Node de = i18n.addNode("de", "nt:folder");
+ de.addMixin("mix:language");
+ de.setProperty("jcr:language", "de");
+ for (Message msg : MESSAGES_DE.values()) {
+ msg.add(de);
+ }
+ getSession().save();
+
+ // some EN content (for parent bundling)
+ Node en = i18n.addNode("en", "nt:folder");
+ en.addMixin("mix:language");
+ en.setProperty("jcr:language", "en");
+ for (Message msg : MESSAGES_EN.values()) {
+ msg.add(en);
+ }
+ getSession().save();
+
+ //SlingTestHelper.printJCR(getSession());
+ }
+
+ // ---------------------------------------------------------------< tests >
+
+ public void test_getString() {
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ for (Message msg : MESSAGES_DE.values()) {
+ assertEquals(msg.message, bundle.getString(msg.key));
+ }
+ }
+
+ public void test_getObject() {
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ for (Message msg : MESSAGES_DE.values()) {
+ assertEquals(msg.message, (String) bundle.getObject(msg.key));
+ }
+ }
+
+ public void test_handle_missing_key() {
+ // test if key is returned if no entry found in repo
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ assertEquals("missing", bundle.getString("missing"));
+ }
+
+ public void test_getKeys() {
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ Enumeration<String> keys = bundle.getKeys();
+ int counter = 0;
+ while (keys.hasMoreElements()) {
+ counter++;
+ String key = keys.nextElement();
+ assertTrue("bundle returned key that is not supposed to be there:
" + key, MESSAGES_DE.containsKey(key));
+ }
+ assertEquals(MESSAGES_DE.size(), counter);
+ }
+
+ public void test_bundle_parenting() {
+ // set parent of resource bundle, test if passed through
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ JcrResourceBundle parentBundle = new JcrResourceBundle(new
Locale("en"), null, resolver);
+ bundle.setParent(parentBundle);
+
+ assertEquals(PARENT_MSG.message, bundle.getObject(PARENT_MSG.key));
+ }
+
+ public void test_search_path() throws Exception {
+ // overwrite stuff in apps
+ Node appsI18n =
getSession().getRootNode().addNode("apps").addNode("i18n", "nt:unstructured");
+ Node de = appsI18n.addNode("de", "nt:folder");
+ de.addMixin("mix:language");
+ de.setProperty("jcr:language", "de");
+ for (Message msg : MESSAGES_DE_APPS.values()) {
+ msg.add(de);
+ }
+ getSession().save();
+
+ // test getString
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
null, resolver);
+ for (Message msg : MESSAGES_DE_APPS.values()) {
+ assertEquals(msg.message, bundle.getString(msg.key));
+ }
+
+ // test getKeys
+ Enumeration<String> keys = bundle.getKeys();
+ int counter = 0;
+ while (keys.hasMoreElements()) {
+ counter++;
+ String key = keys.nextElement();
+ assertTrue("bundle returned key that is not supposed to be there:
" + key, MESSAGES_DE_APPS.containsKey(key));
+ }
+ assertEquals(MESSAGES_DE.size(), counter);
+ }
+
+
+ public void test_basename() throws Exception {
+ // create another de lib with a basename set
+ Node appsI18n = getSession().getRootNode().getNode("libs/i18n");
+ Node de = appsI18n.addNode("de_basename", "nt:unstructured");
+ de.addMixin("mix:language");
+ de.setProperty("jcr:language", "de");
+ de.setProperty("sling:basename", "FOO");
+ for (Message msg : MESSAGES_DE_BASENAME.values()) {
+ msg.add(de);
+ }
+ getSession().save();
+
+ // test getString
+ JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"),
"FOO", resolver);
+ for (Message msg : MESSAGES_DE_BASENAME.values()) {
+ assertEquals(msg.message, bundle.getString(msg.key));
+ }
+
+ // test getKeys
+ Enumeration<String> keys = bundle.getKeys();
+ int counter = 0;
+ while (keys.hasMoreElements()) {
+ counter++;
+ String key = keys.nextElement();
+ assertTrue("bundle returned key that is not supposed to be there:
" + key, MESSAGES_DE_BASENAME.containsKey(key));
+ }
+ assertEquals(MESSAGES_DE.size(), counter);
+ }
+}
Propchange:
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sling/trunk/contrib/extensions/i18n/src/test/java/org/apache/sling/i18n/impl/JcrResourceBundleTest.java
------------------------------------------------------------------------------
svn:keywords = Author Date Id Revision Rev URL
Added: sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties
URL:
http://svn.apache.org/viewvc/sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties?rev=832918&view=auto
==============================================================================
--- sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties
(added)
+++ sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties Wed
Nov 4 23:53:42 2009
@@ -0,0 +1,25 @@
+# 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.
+
+log4j.rootLogger=WARN, stdout
+
+log4j.logger.org.apache.sling.i18n=DEBUG
+
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+#log4j.appender.stdout.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p*
[%t] %c{1}: %m (%F, line %L)\n
+log4j.appender.stdout.layout.ConversionPattern=%d{dd.MM.yyyy HH:mm:ss} *%-5p*
[%t] %c{1}: %m\n
Propchange:
sling/trunk/contrib/extensions/i18n/src/test/resources/log4j.properties
------------------------------------------------------------------------------
svn:eol-style = native