package org.ovirt.engine.core.utils.servlet; import java.io.IOException; import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Properties; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.ovirt.engine.core.utils.LocaleUtils; import org.ovirt.engine.core.utils.ResourceUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * This filter attempts to detect the locale of the user based on the following criteria. * <ol> * <li>The existence of a locale parameter</li> * <li>If no parameter, check for a locale cookie</li> * <li>If no cookie, check the accept headers of the request</li> * <li>If no headers, then default to the US locale</li> * </ol> */ public class LocaleFilter implements Filter { /** * The logger. */ private static final Logger log = LoggerFactory.getLogger(LocaleFilter.class); /** * Constant for parameter and cookie name. */ public static final String LOCALE = "locale"; /** * The root of a path. */ public static final String ROOT_PATH = "/"; /** * The default locale. */ public static final Locale DEFAULT_LOCALE = Locale.US; /** * The extension of the properties file containing the available languages. */ private static final String LANGUAGES_FILE = "languages.properties"; // the property file name @Override public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain) throws IOException, ServletException { Locale locale = determineLocale((HttpServletRequest) request); request.setAttribute(LOCALE, locale); setCookie((HttpServletResponse) response, request.getServletContext(), locale); chain.doFilter(request, response); } /** * Add the {@code Locale} cookie to the response. * @param response The {@code HttpServletResponse} * @param servletContext The {@code ServletContext} to get the request path from. * @param userLocale The {@code Locale} to put in the cookie. */ private void setCookie(final HttpServletResponse response, final ServletContext servletContext, final Locale userLocale) { // Detected locale doesn't match the default locale, set a cookie. Cookie cookie = new Cookie(LocaleFilter.LOCALE, userLocale.toString()); cookie.setPath(ROOT_PATH); cookie.setMaxAge(Integer.MAX_VALUE); // Doesn't expire. cookie.setHttpOnly(true); response.addCookie(cookie); } /** * Check for locale in 4 steps: * <ol> * <li>If no cookies exist, then check for a locale parameter and use that.</li> * <li>Check if the cookie exists which selects the locale.</li> * <li>If no locale parameter exists, check the accept headers.</li> * <li>Default to the US locale</li> * </ol> * @param request The {@code HttpServletRequest} * @return The determined {@code Locale} */ private Locale determineLocale(final HttpServletRequest request) { // Step 1. Locale locale = LocaleUtils.getLocaleFromString(request.getParameter(LOCALE)); // Step 2. if (locale == null) { // No locale parameter. locale = getLocaleFromCookies(request.getCookies()); } // Step 3. if (locale == null) { // No selected locale in cookies. locale = request.getLocale(); } // Step 4. if (locale == null) { // No accept headers. locale = DEFAULT_LOCALE; } Locale resolvedLocale = lookupSupportedLocale(locale, getLocaleKeys()); log.debug("Incoming locale '{}'. Filter determined locale to be '{}'", locale.toLanguageTag(), resolvedLocale.toLanguageTag()); return resolvedLocale; } /** * Loop over the cookies to determine if the locale is set there. * @param cookies The list of {@code Cookie}s. * @return The {@code Locale} if a cookie is found, null otherwise. */ private Locale getLocaleFromCookies(final Cookie[] cookies) { Locale locale = null; if (cookies != null) { for (Cookie cookie: cookies) { if (LOCALE.equalsIgnoreCase(cookie.getName())) { locale = LocaleUtils.getLocaleFromString(cookie.getValue()); break; } } } return locale; } /** * Get a list of available locales. * @return A {@code List} of Locale strings. */ public static List<String> getLocaleKeys() { Properties languages = getLanguageProperties(); @SuppressWarnings("unchecked") List<String> keys = (List<String>) Collections.list(languages.propertyNames()); // Can't just return the unsorted list, it won't match the GWT output. // Apparently GWT does a sort before showing the list as well, which // works out nicely for me. Collections.sort(keys); return keys; } /** * Load the language property file into a Properties object. * @return The {@code Properties} object. */ private static Properties getLanguageProperties() { Properties prop = new Properties(); try { prop = ResourceUtils.loadProperties(LocaleFilter.class, LANGUAGES_FILE); } catch (IOException e) { log.error("Unable to load supported langauges file", e); } return prop; } /** * * @param filteredLocale The locale the filter determined. * @param supportedLocales The list of supported locale strings. * @return The supported locale based on the passed in filteredLocale and list of supported locales. */ private Locale lookupSupportedLocale(final Locale filteredLocale, final List<String> supportedLocales) { for (String supportedLocale: supportedLocales) { Locale locale = LocaleUtils.getLocaleFromString(supportedLocale); @SuppressWarnings("unchecked") List<Locale> localeLookupList = org.apache.commons.lang.LocaleUtils.localeLookupList(locale); for (Locale lookupLocale: localeLookupList) { if (lookupLocale.equals(filteredLocale)) { // Matched on one of the locales. Find the supported one in the list. return locale; } } } return DEFAULT_LOCALE; } @Override public void init(final FilterConfig filterConfig) throws ServletException { // Do nothing } @Override public void destroy() { // Do nothing } }