blob: c29d06ae812f47c973ccfed8e37c22ddea2c223a [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.cms.core.i18n.impl;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.TreeMap;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.cms.AuthorizableWrapper;
import org.apache.sling.cms.i18n.I18NDictionary;
import org.apache.sling.cms.i18n.I18NProvider;
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.ResourceBundleProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.osgi.service.component.annotations.ReferencePolicy;
import org.osgi.service.component.annotations.ReferencePolicyOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("deprecation")
@Component(service = I18NProvider.class)
public class I18NProviderImpl implements I18NProvider {
/**
* Provider that goes through a list of registered providers and takes the first
* non-null responses
*/
private class CombinedBundleProvider implements ResourceBundleProvider {
@Override
public Locale getDefaultLocale() {
// ask all registered providers, use the first one that returns
final ResourceBundleProvider[] sp = sortedProviders;
for (int i = sp.length - 1; i >= 0; i--) {
final ResourceBundleProvider provider = sp[i];
final Locale locale = provider.getDefaultLocale();
if (locale != null) {
log.trace("Found default locale {}", locale);
return locale;
}
}
return null;
}
@Override
public ResourceBundle getResourceBundle(final Locale locale) {
return getResourceBundle(null, locale);
}
@Override
public ResourceBundle getResourceBundle(final String baseName, final Locale locale) {
log.debug("Retrieving resource bundle for {}", locale);
// ask all registered providers, use the first one that returns
final ResourceBundleProvider[] sp = sortedProviders;
for (int i = sp.length - 1; i >= 0; i--) {
final ResourceBundleProvider provider = sp[i];
final ResourceBundle bundle = baseName != null ? provider.getResourceBundle(baseName, locale)
: provider.getResourceBundle(locale);
if (bundle != null) {
log.trace("Using bundle {}", bundle);
return bundle;
}
}
return null;
}
}
private static final Logger log = LoggerFactory.getLogger(I18NProviderImpl.class);
private final DefaultLocaleResolver defaultLocaleResolver = new DefaultLocaleResolver();
private LocaleResolver localeResolver = defaultLocaleResolver;
private final Map<Object, ResourceBundleProvider> providers = new TreeMap<>();
private RequestLocaleResolver requestLocaleResolver = defaultLocaleResolver;
private ResourceBundleProvider[] sortedProviders = new ResourceBundleProvider[0];
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
protected void bindLocaleResolver(final LocaleResolver resolver) {
this.localeResolver = resolver;
}
@Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC, policyOption = ReferencePolicyOption.GREEDY)
protected void bindRequestLocaleResolver(final RequestLocaleResolver resolver) {
this.requestLocaleResolver = resolver;
}
@Reference(service = ResourceBundleProvider.class, cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC)
protected void bindResourceBundleProvider(final ResourceBundleProvider provider, final Map<String, Object> props) {
synchronized (this.providers) {
this.providers.put(ServiceUtil.getComparableForServiceRanking(props, Order.ASCENDING), provider);
this.sortedProviders = this.providers.values().toArray(new ResourceBundleProvider[this.providers.size()]);
}
}
@Override
public I18NDictionary getDictionary(ResourceResolver resolver) {
AuthorizableWrapper wrapper = resolver.adaptTo(AuthorizableWrapper.class);
Locale locale = Optional.ofNullable(wrapper).map(AuthorizableWrapper::getAuthorizable).map(a -> {
try {
Value[] val = a.getProperty("profile/locale");
if (val != null && val.length == 0) {
return val[0].getString();
} else {
return Locale.ENGLISH.toLanguageTag();
}
} catch (RepositoryException e) {
return Locale.ENGLISH.toLanguageTag();
}
}).map(Locale::forLanguageTag).orElse(Locale.ENGLISH);
CombinedBundleProvider cbp = new CombinedBundleProvider();
return new I18NDictionaryImpl(cbp.getResourceBundle(locale));
}
@Override
public I18NDictionary getDictionary(SlingHttpServletRequest request) {
return getDictionary(request.getResourceResolver());
}
protected void unbindLocaleResolver(final LocaleResolver resolver) {
if (this.localeResolver == resolver) {
this.localeResolver = defaultLocaleResolver;
}
}
protected void unbindRequestLocaleResolver(final RequestLocaleResolver resolver) {
if (this.requestLocaleResolver == resolver) {
this.requestLocaleResolver = defaultLocaleResolver;
}
}
// ---------- internal -----------------------------------------------------
protected void unbindResourceBundleProvider(final ResourceBundleProvider provider,
final Map<String, Object> props) {
synchronized (this.providers) {
this.providers.remove(ServiceUtil.getComparableForServiceRanking(props, Order.ASCENDING));
this.sortedProviders = this.providers.values().toArray(new ResourceBundleProvider[this.providers.size()]);
}
}
}