LocalizationFilter.java :  » Database-ORM » Velosurf » velosurf » web » l10n » Java Open Source

Java Open Source » Database ORM » Velosurf 
Velosurf » velosurf » web » l10n » LocalizationFilter.java
/*
 * Copyright 2003 The Apache Software Foundation.
 *
 * Licensed 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 velosurf.web.l10n;

import velosurf.util.Logger;
import velosurf.util.StringLists;
import velosurf.util.ServletLogWriter;
import velosurf.util.ToolFinder;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Cookie;
import java.io.IOException;
import java.util.Locale;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Set;
import java.util.Collections;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

/**
 * <p>Localization filter. It's goal is to redirect or forward incoming unlocalized http requests (depending on the
 * choosen method, <code>FORWARD</code> or <code>REDIRECT</code>) towards an address taking into account the best match
 * between requested locales and supported locales, and also to deduce the locale from URLS (when <code>REDIRECT</code>
 * is used).</p>
 *
 * <p>Optional init parameters:
 * <ul>
 * <li><code>supported-locales</code>: comma separated list of supported locales ; if not provided, there is an attempt to programatically determine it<sup>(1)</sup>.
 * No default value provided.</li>
 * <li><code>default-locale</code>: the locale to be used by default (after four checks: the incoming URI, the session, cookies, and the request headers).
 * No default value provided.</li>
 * <li><code>localization-method</code>: <code>forward</code> or <code>redirect</code>, default is <code>redirect</code>.
 * <li><code>match-host</code> & <code>rewrite-host</code>: not yet implemented.<sup>(2)</sup></li>
 * <li><code>match-uri</code> & <code>rewrite-uri</code>: the regular expression against which an unlocalized uri is matched, and the replacement uri, where
 * <code>@</code> represents the locale and $1, $2, ... the matched sub-patterns. Defaults are <code>^/(.*)$</code>
 * for <code>match-uri</code> and <code>/@/$1</code> for rewrite-uri.(2)</li>
 * <li><code>match-query-string</code> & <code>rewrite-query-string</code>: not yet implemented.(2)</li>
 * <li><code>match-url</code> & <code>rewrite-url</code>: not yet implemented.(2)</li>
 * </ul>
 * </p>
 *
 * <p><b><small>(1)</small></b> for now, to find supported locales if this parameter is not provided,
 * the filter try to use the <code>rewrite-uri</code> param and to check for the existence of corresponding directories (only if the rewriting
 * string contains a pattern like '/@/', that is if you use directories to store localized sites).
 * <p><b><small>(2)</small></b> The different <code>match-</code> and <code>rewrite-</code> parameters pairs are mutually exclusive.
 * All matches are case-insensitive.</p>
 *
 * <p>When the <code>redirect</code> method is used, these supplementary parameters (mutually exclusive) allow the filter to
 * know whether or not an incoming URI is localized.
 * <ul>
 * <li><code>inspect-host</code>: not yet implemented.</li>
 * <li><code>inspect-uri</code>: default is <code>^/(.+)(?:/|$)</code>.
 * <li><code>inspect-query-string</code>: not yet implemented.</li>
 * <li><code>inspect-url</code>: not yet implemented.</li>
 * </ul>
 * </p>
 *
 *  @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
 */

public class LocalizationFilter implements Filter {

    /** filter config. */
    private FilterConfig config = null;

    /** supported locales. */
    private List<Locale> supportedLocales = null;
    /** default locale. */
    private Locale defaultLocale = null;

    /** seconds in year (for setting cookies age). */
    private static int SECONDS_IN_YEAR = 31536000;

    /** default match uri. */
    private static String defaultMatchUri = "^/(.*)$";
    /** default rewrite uri. */
    private static String defaultRewriteUri = "/@/$1";
    /** default inspect uri. */
    private static String defaultInspectUri = "^/(.+)(?:/|$)";
    /** match uri. */
    private  Pattern matchUri = null;
    /** rewrite uri */
    private String rewriteUri = null;
    /** inspect uri. */
    private Pattern inspectUri = null;

    /** forward method constant. */
    private static final int FORWARD = 1;
    /** redirect method constant. */
    private static final int REDIRECT = 2;

    /** localization method. */
    private int l10nMethod = REDIRECT;

    /** initialization.
     *
     * @param config filter config
     * @throws ServletException
     */
    public synchronized void init(FilterConfig config) throws ServletException {
        this.config = config;

        /* logger initialization */
        if (!Logger.isInitialized()) {
            Logger.setWriter(new ServletLogWriter(config.getServletContext()));
        }

        String param;

        /* uri */
        matchUri = Pattern.compile(getInitParameter("match-uri",defaultMatchUri),Pattern.CASE_INSENSITIVE);
        rewriteUri = getInitParameter("rewrite-uri",defaultRewriteUri);
        inspectUri = Pattern.compile(getInitParameter("inspect-uri",defaultInspectUri),Pattern.CASE_INSENSITIVE);

        /* method */
        param = getInitParameter("localization-method","redirect");
        if (param.equalsIgnoreCase("redirect")) {
            l10nMethod = REDIRECT;
        } else if (param.equalsIgnoreCase("forward")) {
            l10nMethod = FORWARD;
        } else {
            Logger.error("LocalizationFilter: '"+param+"' is not a valid l10n method; should be 'forward' or 'redirect'.");
        }

        /* supported locales */
        findSupportedLocales(this.config);

        /* default locale */
        defaultLocale = getMatchedLocale(getInitParameter("default-locale"));
    }

    /**
     * Filtering.
     * @param servletRequest request
     * @param servletResponse response
     * @param chain filter chain
     * @throws IOException
     * @throws ServletException
     */
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest)servletRequest;
        HttpSession session = request.getSession(true); /* we'll store the active locale in it */
        HttpServletResponse response = (HttpServletResponse)servletResponse;

        Locale locale = null;

        /* should an action (forward/redirect) be taken by the filter? */
        boolean shouldAct = true;

        /* Now, what is the current locale ?
           Guess #1 is the URI, if already localized (only for REDIRECT method).
           Guess #2 is the session attribute 'active-locale'.
           Guess #3 is a cookie 'used-locale'.
           Guess #4 is from the Accepted-Language header.
        */

  //      Logger.trace("l10n: URI ="+request.getRequestURI());

        /* Guess #1 - if using redirect method, deduce from URI (and, while looking at URI, fills the shouldAct vairable) */
        if (l10nMethod == REDIRECT) {
            Matcher matcher = inspectUri.matcher(request.getRequestURI());
            if (matcher.find()) {
                String candidate = matcher.group(1);
                locale = getMatchedLocale(candidate);
                if (locale != null) {
                    shouldAct = false;
                }
            }
//            Logger.trace("l10n: URI locale = "+locale);
        } else {
            /* for the forward method, shouldAct rule is: always only when not already forwarded */
            Boolean forwarded = (Boolean)request.getAttribute("velosurf.l10n.l10n-forwarded");
            if(forwarded != null && forwarded.booleanValue()) {
                shouldAct = false;
            }
        }

        if (locale == null) {
            /* Guess #2 - is there an attribute in the session? */
            locale = (Locale)session.getAttribute("velosurf.l10n.active-locale");
//            Logger.trace("l10n: session locale = "+locale);

            if (locale == null) {
                /* Guess #3 - is there a cookie?*/
                Cookie cookies[] = request.getCookies();
                if (cookies != null) {
                    for(Cookie cookie:cookies) {
                        if ("velosurf.l10n.active-locale".equals(cookie.getName())) {
                            locale = getMatchedLocale(cookie.getValue());
                        }
                    }
                }
//                Logger.trace("l10n: cookies locale = "+locale);

                if(locale == null) {
                    /* Guess #4 - use the Accepted-Language HTTP header */
                    List<Locale> requestedLocales = getRequestedLocales(request);
                    locale = getPreferredLocale(requestedLocales);
                    Logger.trace("l10n: Accepted-Language header best matching locale = "+locale);
                }
            }
        }

        if (locale == null && defaultLocale != null) {
            locale = defaultLocale;
        }
/* not needed - the tool should find the active locale in the session
        if (locale != null) {
            Localizer tool = ToolFinder.findSessionTool(session,Localizer.class);
            if (tool != null) {
                tool.setLocale(locale);
            } else {
                Logger.warn("l10n: cannot find any Localizer tool!");
            }
        }
*/
        /* sets the session atribute and the cookies */
  //      Logger.trace("l10n: setting session current locale to "+locale);
        session.setAttribute("velosurf.l10n.active-locale",locale);
        Cookie localeCookie = new Cookie("velosurf.l10n.active-locale",locale.toString());
        localeCookie.setPath("/");
        localeCookie.setMaxAge(SECONDS_IN_YEAR);
        response.addCookie(localeCookie);

        Matcher match = matchUri.matcher(request.getRequestURI());
        shouldAct &= match.find();

        if (shouldAct) {
            //  && (i = rewriteUri.indexOf("@")) != -1) ?

            String rewriteUri = this.rewriteUri.replaceFirst("@",locale.toString());
            String newUri = match.replaceFirst(rewriteUri);
            RequestDispatcher dispatcher;

            String query = request.getQueryString();
            if (query == null) {
                query = "";
            } else {
                query = "?" + query;
            }

            switch(l10nMethod) {
                case REDIRECT:
                    Logger.trace("l10n: redirecting request to "+newUri+query);
                    response.sendRedirect(newUri+query);
                    break;
                case FORWARD:
                    dispatcher = config.getServletContext().getRequestDispatcher(newUri+query);
                    if (dispatcher == null) {
                        Logger.error("l10n: cannot find a request dispatcher for path '"+newUri+"'");
                        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
                    } else {
                        Logger.trace("l10n: forwarding request to "+newUri+query);
                        request.setAttribute("velosurf.l10n.l10n-forwarded",Boolean.valueOf(shouldAct));
                        dispatcher.forward(request,response);
                    }
                    break;
            }
        } else {
//            Logger.trace("l10n: letting request pass towards "+request.getRequestURI());
            chain.doFilter(request,response);
        }
    }

    /** Find supported locales.
     *
     * @param config filter config
     */
    private void findSupportedLocales(FilterConfig config) {
        /* look in the filter init-params */
        String param = config.getInitParameter("supported-locales");
        if (param == null) {
            /* look in the webapp context-params */
            param = config.getServletContext().getInitParameter("supported-locales");
        }

        if (param == null) {
            /* try to determine it */
            int i;
            if (rewriteUri != null && (i=rewriteUri.indexOf("@")) != -1) {
                supportedLocales = guessSupportedLocales(this.config.getServletContext(),rewriteUri.substring(0,i));
                if(Logger.getLogLevel() <= Logger.TRACE_ID) {
                    Logger.trace("l10n: supported locales = " + StringLists.join(Arrays.asList(supportedLocales),","));
                }
                if (supportedLocales != null && supportedLocales.size() > 0) {
                    return;
                }
            }
        } else {
            supportedLocales = new ArrayList<Locale>();
            String[] list = param.split(",");
            for(String code:list) {
                supportedLocales.add(new Locale(code));
            }
        }
        if(supportedLocales != null && supportedLocales.size() > 0) {
            /* let other objects see it?
            config.getServletContext().setAttribute("velosurf.l10n.supported-locales",supportedLocales);
             */
        } else {
            Logger.error("l10n: Cannot find any supported locale! Please add a 'supported-locales' context-param.");
        }
    }

    /** Helper function.
     *
     * @param key
     * @return init-parameter
     */
    private String getInitParameter(String key) {
        return config.getInitParameter(key);
    }

    /** Helper function.
     *
     * @param key
     * @param defaultValue
     * @return init-parameter
     */
    private String getInitParameter(String key,String defaultValue) {
        String param = config.getInitParameter(key);
        return param == null ? defaultValue : param;
    }

    /** Destroy the filter.
     *
     */
    public void destroy() {
    }

    /**
     * Guess supported locales.
     * @param ctx servlet context
     * @param path path
     * @return list of locales
     */
    private List<Locale> guessSupportedLocales(ServletContext ctx,String path) {
        List<Locale> locales = new ArrayList<Locale>();
        String languages[] = Locale.getISOLanguages();
        String countries[] = Locale.getISOCountries();
        Arrays.sort(languages);
        Arrays.sort(countries);
        String language,country;
        for(String resource:(Set<String>)ctx.getResourcePaths(path)) {
            /* first, it must be a path */
            if (resource.endsWith("/")) {
                int len = resource.length();
                int i = resource.lastIndexOf('/',len-2);
                String locale = resource.substring(i+1,len-1);
                if((i=locale.indexOf('_'))!=-1) {
                    language = locale.substring(0,i);
                    country = locale.substring(i+1);
                } else {
                    language = locale;
                    country = null;
                }
                /* then it must contains valid language and country codes */
                if (Arrays.binarySearch(languages,language) >= 0
                        && (country == null || Arrays.binarySearch(countries,country) >= 0 )) {
                    /* looks ok... */
                    locales.add(country == null ? new Locale(language) : new Locale(language,country));
                }
            }
        }
        supportedLocales = locales;
        return locales;
    }

    /** get the list of requested locales.
     *
     * @param request request
     * @return list of locales
     */
    private List<Locale> getRequestedLocales(HttpServletRequest request) {
        List<Locale> list = (List<Locale>)Collections.list(request.getLocales());
        if(/*list.size() == 0 && */defaultLocale != null) {
            list.add(defaultLocale);
        }
        return list;
    }

    /**
     * get matched locale.
     * @param candidate candidate
     * @return locale
     */
    private Locale getMatchedLocale(String candidate) {
        if(candidate == null) return null;
        if (supportedLocales == null) {
            Logger.error("l10n: the list of supported locales is empty!");
            return null;
        }
        for(Locale locale:supportedLocales) {
            if (candidate.startsWith(locale.toString())) {
                return locale;
            }
        }
        for(Locale locale:supportedLocales) {
            if (locale.toString().startsWith(candidate)) {
                return locale;
            }
        }
        return null;
    }

    /**
     * Get preferred locale.
     * @param requestedLocales requested locales
     * @return preferred locale
     */
    private Locale getPreferredLocale(List<Locale> requestedLocales) {
        for(Locale locale:requestedLocales) {
            if(supportedLocales.contains(locale)) {
                return locale;
            }
        }
        /* still there? Ok, second pass without the country. */
        for(Locale locale:requestedLocales) {
            if (locale.getCountry() != null) {
                locale = new Locale(locale.getLanguage());
                if(supportedLocales.contains(locale)) {
                    return locale;
                }
            }
        }
        Logger.warn("l10n: did not find a matching locale for "+StringLists.join(requestedLocales,","));
        /* then return the default locale, even if it doesn't match...
        if(defaultLocale != null) {
            return defaultLocale;
        }*/
        /* Oh, well... */
        return null;
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.