blob: d8d7fbf37fa20778bb382995d5f1c2f2d00dad96 [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.IOException;
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.ResourceBundle;
import java.util.TreeMap;
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 org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.ReferencePolicyOption;
import org.apache.felix.scr.annotations.sling.SlingFilter;
import org.apache.felix.scr.annotations.sling.SlingFilterScope;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
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.framework.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The <code>I18NFilter</code> class is a request level filter, which provides
* the resource bundle for the current request.
*/
@SlingFilter(generateService = true,
order = 700, scope = { SlingFilterScope.REQUEST, SlingFilterScope.ERROR })
@Properties({
@Property(name = "pattern", value="/.*"),
@Property(name = Constants.SERVICE_DESCRIPTION, value = "Internationalization Support Filter"),
@Property(name = Constants.SERVICE_VENDOR, value = "The Apache Software Foundation") })
public class I18NFilter implements Filter {
/** Logger */
private final static Logger LOG = LoggerFactory.getLogger(I18NFilter.class.getName());
private final DefaultLocaleResolver DEFAULT_LOCALE_RESOLVER = new DefaultLocaleResolver();
@Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY)
private volatile LocaleResolver localeResolver = DEFAULT_LOCALE_RESOLVER;
@Reference(cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC, policyOption=ReferencePolicyOption.GREEDY)
private volatile RequestLocaleResolver requestLocaleResolver = DEFAULT_LOCALE_RESOLVER;
@Reference(name = "resourceBundleProvider",
referenceInterface = ResourceBundleProvider.class,
cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE,
policy = ReferencePolicy.DYNAMIC)
private final Map<Object, ResourceBundleProvider> providers = new TreeMap<Object, ResourceBundleProvider>();
private volatile ResourceBundleProvider[] sortedProviders = new ResourceBundleProvider[0];
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)
*/
@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);
}
} else {
request = new I18NHttpServletRequest(request,
combinedProvider, requestLocaleResolver);
}
// and forward the request
chain.doFilter(request, response);
}
/**
* @see javax.servlet.Filter#destroy()
*/
@Override
public void destroy() {
synchronized(this) {
initCount--;
}
}
// ---------- SCR Integration ----------------------------------------------
protected void bindLocaleResolver(final LocaleResolver resolver) {
this.localeResolver = resolver;
}
protected void unbindLocaleResolver(final LocaleResolver resolver) {
if (this.localeResolver == resolver) {
this.localeResolver = DEFAULT_LOCALE_RESOLVER;
}
}
protected void bindRequestLocaleResolver(final RequestLocaleResolver resolver) {
this.requestLocaleResolver = resolver;
}
protected void unbindRequestLocaleResolver(final RequestLocaleResolver resolver) {
if (this.requestLocaleResolver == resolver) {
this.requestLocaleResolver = DEFAULT_LOCALE_RESOLVER;
}
}
protected void bindResourceBundleProvider(final ResourceBundleProvider provider, final Map<String, Object> props) {
synchronized ( this.providers ) {
this.providers.put(ServiceUtil.getComparableForServiceRanking(props), provider);
this.sortedProviders = this.providers.values().toArray(new ResourceBundleProvider[this.providers.size()]);
}
}
protected void unbindResourceBundleProvider(final ResourceBundleProvider provider, final Map<String, Object> props) {
synchronized ( this.providers ) {
this.providers.remove(ServiceUtil.getComparableForServiceRanking(props));
this.sortedProviders = this.providers.values().toArray(new ResourceBundleProvider[this.providers.size()]);
}
}
// ---------- internal -----------------------------------------------------
/** 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[] providers = sortedProviders;
for(int i=providers.length-1; i >= 0; i--) {
final ResourceBundleProvider provider = providers[i];
final Locale locale = provider.getDefaultLocale();
if (locale != null) {
return locale;
}
}
return null;
}
@Override
public ResourceBundle getResourceBundle(final Locale locale) {
// ask all registered providers, use the first one that returns
final ResourceBundleProvider[] providers = sortedProviders;
for(int i=providers.length-1; i >= 0; i--) {
final ResourceBundleProvider provider = providers[i];
final ResourceBundle bundle = provider.getResourceBundle(locale);
if (bundle != null) {
return bundle;
}
}
return null;
}
@Override
public ResourceBundle getResourceBundle(final String baseName, final Locale locale) {
// ask all registered providers, use the first one that returns
final ResourceBundleProvider[] providers = sortedProviders;
for(int i=providers.length-1; i >= 0; i--) {
final ResourceBundleProvider provider = providers[i];
final ResourceBundle bundle = provider.getResourceBundle(baseName, locale);
if (bundle != null) {
return bundle;
}
}
return null;
}
}
// ---------- internal class -----------------------------------------------
private static class I18NHttpServletRequest
extends HttpServletRequestWrapper {
private final ResourceBundleProvider bundleProvider;
private final RequestLocaleResolver localeResolver;
private Locale locale;
private List<Locale> localeList;
private ResourceBundle resourceBundle;
I18NHttpServletRequest(final ServletRequest delegatee,
final ResourceBundleProvider bundleProvider,
final RequestLocaleResolver localeResolver) {
super((HttpServletRequest)delegatee);
this.bundleProvider = bundleProvider;
this.localeResolver = localeResolver;
}
@Override
public Locale getLocale() {
if (locale == null) {
locale = this.getLocaleList().get(0);
}
return locale;
}
@Override
public Enumeration<?> getLocales() {
return Collections.enumeration(getLocaleList());
}
@Override
public Object getAttribute(final String name) {
if ( ResourceBundleProvider.BUNDLE_REQ_ATTR.equals(name) ) {
if ( this.resourceBundle == null && this.bundleProvider != null) {
this.resourceBundle = this.bundleProvider.getResourceBundle(this.getLocale());
}
return this.resourceBundle;
}
return super.getAttribute(name);
}
private List<Locale> getLocaleList() {
if (localeList == null) {
List<Locale> resolved = localeResolver.resolveLocale((HttpServletRequest)this.getRequest());
this.localeList = (resolved != null && !resolved.isEmpty())
? resolved
: Collections.singletonList(this.bundleProvider.getDefaultLocale());
}
return localeList;
}
}
private static class BaseI18NSlingHttpServletRequest
extends SlingHttpServletRequestWrapper {
protected final ResourceBundleProvider bundleProvider;
BaseI18NSlingHttpServletRequest(final ServletRequest delegatee,
final ResourceBundleProvider bundleProvider) {
super((SlingHttpServletRequest) delegatee);
this.bundleProvider = bundleProvider;
}
@Override
public ResourceBundle getResourceBundle(Locale locale) {
return getResourceBundle(null, locale);
}
@Override
public ResourceBundle getResourceBundle(String baseName, Locale locale) {
if (bundleProvider != null) {
if (locale == null) {
locale = getLocale();
}
try {
return bundleProvider.getResourceBundle(baseName, locale);
} catch (MissingResourceException mre) {
LOG.warn(
"getResourceBundle: Cannot get ResourceBundle from provider",
mre);
}
} else {
LOG.info("getResourceBundle: ResourceBundleProvider not available, calling default implementation");
}
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) {
if ( ResourceBundleProvider.BUNDLE_REQ_ATTR.equals(name) ) {
final Object superValue = super.getAttribute(name);
return (superValue != null ? superValue : this.getResourceBundle(null));
}
return super.getAttribute(name);
}
@Override
public Locale getLocale() {
if (locale == null) {
locale = this.getLocaleList().get(0);
}
return locale;
}
@Override
public Enumeration<?> getLocales() {
return Collections.enumeration(getLocaleList());
}
private List<Locale> getLocaleList() {
if (localeList == null) {
List<Locale> resolved = localeResolver.resolveLocale(this.getSlingRequest());
this.localeList = (resolved != null && !resolved.isEmpty())
? resolved
: Collections.singletonList(this.bundleProvider.getDefaultLocale());
}
return localeList;
}
}
}