blob: c23d539919d3b4916109131d83485d906a3f2610 [file] [log] [blame]
/*
* 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.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import javax.jcr.Binary;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.query.Query;
import javax.jcr.query.QueryResult;
import javax.jcr.query.RowIterator;
import javax.naming.NamingException;
import javax.servlet.http.HttpServletRequest;
import org.apache.jackrabbit.JcrConstants;
import org.apache.sling.api.resource.AbstractResource;
import org.apache.sling.api.resource.PersistenceException;
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.api.resource.ValueMap;
import org.apache.sling.api.wrappers.ValueMapDecorator;
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;
@Override
public void setUp() throws Exception {
super.setUp();
cleanRepository();
RepositoryUtil.registerNodeType(getSession(), getClass()
.getResourceAsStream("/SLING-INF/nodetypes/jcrlanguage.cnd"));
RepositoryUtil.registerNodeType(getSession(), getClass()
.getResourceAsStream("/SLING-INF/nodetypes/message.cnd"));
resolver = new ResourceResolver() {
@Override
public Iterator<Resource> findResources(String query,
String language) {
try {
final Query q = getSession().getWorkspace().getQueryManager().createQuery(query, language);
final QueryResult result = q.execute();
final NodeIterator nodes = result.getNodes();
return new Iterator<Resource>() {
@Override
public boolean hasNext() {
return nodes.hasNext();
}
@Override
public Resource next() {
final Node node = nodes.nextNode();
return new TestResource(resolver, node);
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
};
} catch (NamingException ne) {
return null;
} catch (RepositoryException re) {
return null;
}
}
@Override
public Resource getResource(Resource base, String path) {
// TODO Auto-generated method stub
return null;
}
@Override
public Resource getResource(String path) {
try {
final Node n = getSession().getNode(path);
return new TestResource(resolver, n);
} catch (NamingException ne) {
//ignore
} catch (RepositoryException re) {
//ignore
}
return null;
}
@Override
public String[] getSearchPath() {
return new String[] {"/apps/", "/libs/"};
}
@Override
public Iterator<Resource> listChildren(final Resource parent) {
try {
final Node n = getSession().getNode(parent.getPath());
final NodeIterator nodes = n.getNodes();
return new Iterator<Resource>() {
@Override
public boolean hasNext() {
return nodes.hasNext();
}
@Override
public Resource next() {
final Node node = nodes.nextNode();
return new TestResource(resolver, node);
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
};
} catch ( final RepositoryException re) {
// ignore
return null;
} catch ( final NamingException e) {
// ignore
return null;
}
}
@Override
public String map(HttpServletRequest request, String resourcePath) {
// TODO Auto-generated method stub
return null;
}
@Override
public String map(String resourcePath) {
// TODO Auto-generated method stub
return null;
}
@Override
public Iterator<Map<String, Object>> queryResources(String query,
String language) {
try {
final Query q = getSession().getWorkspace().getQueryManager().createQuery(query, language);
final QueryResult result = q.execute();
final String[] colNames = result.getColumnNames();
final RowIterator rows = result.getRows();
return new Iterator<Map<String, Object>>() {
@Override
public boolean hasNext() {
return rows.hasNext();
}
@Override
public Map<String, Object> next() {
Map<String, Object> row = new HashMap<String, Object>();
try {
Value[] values = rows.nextRow().getValues();
for (int i = 0; i < values.length; i++) {
Value v = values[i];
if (v != null) {
row.put(colNames[i], values[i].getString());
}
}
} catch (RepositoryException re) {
// ignore
}
return row;
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
};
} catch (NamingException ne) {
return null;
} catch (RepositoryException re) {
return null;
}
}
@Override
public Resource resolve(HttpServletRequest request, String absPath) {
// TODO Auto-generated method stub
return null;
}
@Override
public Resource resolve(HttpServletRequest request) {
// TODO Auto-generated method stub
return null;
}
@Override
public Resource resolve(String absPath) {
// TODO Auto-generated method stub
return null;
}
@Override
public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) {
// TODO Auto-generated method stub
return null;
}
@Override
public void close() {
// nothing to do
}
@Override
public String getUserID() {
return null;
}
@Override
public boolean isLive() {
return true;
}
@Override
public ResourceResolver clone(Map<String, Object> authenticationInfo) {
return null;
}
@Override
public Iterator<String> getAttributeNames() {
return null;
}
@Override
public Object getAttribute(String name) {
return null;
}
@Override
public Iterable<Resource> getChildren(Resource parent) {
// TODO Auto-generated method stub
return null;
}
@Override
public void delete(Resource resource) throws PersistenceException {
// TODO Auto-generated method stub
}
@Override
public Resource create(Resource parent, String name,
Map<String, Object> properties) throws PersistenceException {
// TODO Auto-generated method stub
return null;
}
@Override
public void revert() {
// TODO Auto-generated method stub
}
@Override
public void commit() throws PersistenceException {
// TODO Auto-generated method stub
}
@Override
public boolean hasChanges() {
// TODO Auto-generated method stub
return false;
}
@Override
public String getParentResourceType(Resource resource) {
// TODO Auto-generated method stub
return null;
}
@Override
public String getParentResourceType(String resourceType) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isResourceType(final Resource resource, final String resourceType) {
return resourceType.equals(resource.getResourceType());
}
@Override
public void refresh() {
// TODO Auto-generated method stub
}
@Override
public Resource getParent(Resource child) {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean hasChildren(Resource resource) {
// TODO Auto-generated method stub
return false;
}
@Override
public Resource copy(String srcAbsPath, String destAbsPath) throws PersistenceException {
// TODO Auto-generated method stub
return null;
}
@Override
public Resource move(String srcAbsPath, String destAbsPath) throws PersistenceException {
// TODO Auto-generated method stub
return null;
}
};
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 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_EN_DASH_US = new LinkedHashMap<String, Message>();
public static final Map<String, Message> MESSAGES_EN_UNDERSCORE_UK = new LinkedHashMap<String, Message>();
public static final Map<String, Message> MESSAGES_EN_UNDERSCORE_AU = 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", "K�che", 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", "L�ffel", true));
// 5. not present in DE
add(MESSAGES_EN, PARENT_MSG);
add(MESSAGES_EN_DASH_US, new Message("", "pigment", "color", false));
add(MESSAGES_EN_UNDERSCORE_UK, new Message("", "pigment", "colour", false));
add(MESSAGES_EN_UNDERSCORE_AU, new Message("", "pigment", "colour", false));
// 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();
// some EN US content
Node enDashUS = i18n.addNode("en-US", "nt:folder");
enDashUS.addMixin("mix:language");
enDashUS.setProperty("jcr:language", "en-US");
for (Message msg : MESSAGES_EN_DASH_US.values()) {
msg.add(enDashUS);
}
getSession().save();
// some EN UK content
Node enUnderscoreUK = i18n.addNode("en_UK", "nt:folder");
enUnderscoreUK.addMixin("mix:language");
enUnderscoreUK.setProperty("jcr:language", "en_UK");
for (Message msg : MESSAGES_EN_UNDERSCORE_UK.values()) {
msg.add(enUnderscoreUK);
}
getSession().save();
// some EN AU content
Node enUnderscoreAU = i18n.addNode("en_au", "nt:folder");
enUnderscoreAU.addMixin("mix:language");
enUnderscoreAU.setProperty("jcr:language", "en_au");
for (Message msg : MESSAGES_EN_UNDERSCORE_AU.values()) {
msg.add(enUnderscoreAU);
}
getSession().save();
}
// ---------------------------------------------------------------< 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));
}
bundle = new JcrResourceBundle(new Locale("en", "us"), null, resolver);
for (Message msg : MESSAGES_EN_DASH_US.values()) {
assertEquals(msg.message, bundle.getString(msg.key));
}
bundle = new JcrResourceBundle(new Locale("en", "uk"), null, resolver);
for (Message msg : MESSAGES_EN_UNDERSCORE_UK.values()) {
assertEquals(msg.message, bundle.getString(msg.key));
}
bundle = new JcrResourceBundle(new Locale("en", "au"), null, resolver);
for (Message msg : MESSAGES_EN_UNDERSCORE_AU.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);
bundle.setParent(new RootResourceBundle());
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);
parentBundle.setParent(new RootResourceBundle());
assertEquals(PARENT_MSG.message, bundle.getObject(PARENT_MSG.key));
assertEquals("missing", bundle.getString("missing"));
}
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_outside_search_path() throws Exception {
Node libsI18n = getSession().getRootNode().getNode("libs/i18n");
libsI18n.remove();
// dict outside search path: /content
Node contentI18n = getSession().getRootNode().addNode("content").addNode("i18n", "nt:unstructured");
Node de = contentI18n.addNode("de", "nt:folder");
de.addMixin("mix:language");
de.setProperty("jcr:language", "de");
for (Message msg : MESSAGES_DE.values()) {
msg.add(de);
}
getSession().save();
// test if /content dictionary is read at all
JcrResourceBundle bundle = new JcrResourceBundle(new Locale("de"), null, resolver);
for (Message msg : MESSAGES_DE.values()) {
assertEquals(msg.message, bundle.getString(msg.key));
}
// now overwrite /content dict in /libs
libsI18n = getSession().getRootNode().getNode("libs").addNode("i18n", "nt:unstructured");
de = libsI18n.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 if /libs (something in the search path) overlays /content (outside the search path)
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", new String[]{"FOO", "BAR"});
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 getString
bundle = new JcrResourceBundle(new Locale("de"), "BAR", 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);
}
public void test_json_dictionary() throws Exception {
Node appsI18n = getSession().getRootNode().addNode("apps").addNode("i18n", "nt:unstructured");
Node deJson = appsI18n.addNode("de.json", "nt:file");
deJson.addMixin("mix:language");
deJson.setProperty("jcr:language", "de");
Node content = deJson.addNode("jcr:content", "nt:resource");
content.setProperty("jcr:mimeType", "application/json");
// manually creating json file, good enough for the test
StringBuilder json = new StringBuilder();
json.append("{");
for (Message msg : MESSAGES_DE_APPS.values()) {
json.append("\"").append(msg.key).append("\": \"");
json.append(msg.message).append("\",\n");
}
json.append("}");
InputStream stream = new ByteArrayInputStream(json.toString().getBytes());
Binary binary = getSession().getValueFactory().createBinary(stream);
content.setProperty("jcr:data", binary);
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);
}
private class TestResource extends AbstractResource {
private final Node node;
private final ResourceResolver resolver;
public TestResource(final ResourceResolver resolver, final Node xnode) {
this.node = xnode;
this.resolver = resolver;
}
@Override
public String getResourceType() {
try {
return this.node.getPrimaryNodeType().getName();
} catch ( final RepositoryException re) {
return "<unknown>";
}
}
@Override
public String getResourceSuperType() {
// TODO Auto-generated method stub
return null;
}
@Override
public ResourceResolver getResourceResolver() {
return this.resolver;
}
@Override
public ResourceMetadata getResourceMetadata() {
return new ResourceMetadata();
}
@Override
public String getPath() {
try {
return node.getPath();
} catch ( final RepositoryException re ) {
throw new RuntimeException(re);
}
}
@Override
public <AdapterType> AdapterType adaptTo(
Class<AdapterType> type) {
if ( type == ValueMap.class) {
try {
final Map<String, Object> props = new HashMap<String, Object>();
if ( node.hasProperty(JcrResourceBundle.PROP_LANGUAGE) ) {
props.put(JcrResourceBundle.PROP_LANGUAGE, node.getProperty(JcrResourceBundle.PROP_LANGUAGE).getString());
}
if ( node.hasProperty(JcrResourceBundle.PROP_BASENAME) ) {
Property propBasename = node.getProperty(JcrResourceBundle.PROP_BASENAME);
if (propBasename.isMultiple()) {
String[] basenames = new String[propBasename.getValues().length];
int i=0;
for (Value basename : propBasename.getValues()) {
basenames[i++] = basename.getString();
}
props.put(JcrResourceBundle.PROP_BASENAME, basenames);
} else {
props.put(JcrResourceBundle.PROP_BASENAME, propBasename.getString());
}
}
if ( node.hasProperty(JcrResourceBundle.PROP_KEY) ) {
props.put(JcrResourceBundle.PROP_KEY, node.getProperty(JcrResourceBundle.PROP_KEY).getString());
}
if ( node.hasProperty(JcrResourceBundle.PROP_VALUE) ) {
props.put(JcrResourceBundle.PROP_VALUE, node.getProperty(JcrResourceBundle.PROP_VALUE).getString());
}
if ( node.hasProperty(JcrResourceBundle.PROP_MIXINS) ) {
final Value[] values = node.getProperty(JcrResourceBundle.PROP_MIXINS).getValues();
final String[] names = new String[values.length];
for(int i=0;i<values.length;i++) {
names[i] = values[i].getString();
}
props.put(JcrResourceBundle.PROP_MIXINS, names);
}
return (AdapterType)new ValueMapDecorator(props);
} catch ( final RepositoryException re ) {
throw new RuntimeException(re);
}
}
if (type == InputStream.class) {
try {
if (node.hasNode(JcrConstants.JCR_CONTENT)) {
return (AdapterType) node.getNode(JcrConstants.JCR_CONTENT).getProperty(JcrConstants.JCR_DATA).getBinary().getStream();
} else {
return null;
}
} catch (RepositoryException e) {
throw new RuntimeException(e);
}
}
if (type == Node.class) {
return (AdapterType)node;
}
return super.adaptTo(type);
}
};
}