001/*
002 * #%L
003 * Netarchivesuite - heritrix 3 monitor
004 * %%
005 * Copyright (C) 2005 - 2018 The Royal Danish Library, 
006 *             the National Library of France and the Austrian National Library.
007 * %%
008 * This program is free software: you can redistribute it and/or modify
009 * it under the terms of the GNU Lesser General Public License as
010 * published by the Free Software Foundation, either version 2.1 of the
011 * License, or (at your option) any later version.
012 * 
013 * This program is distributed in the hope that it will be useful,
014 * but WITHOUT ANY WARRANTY; without even the implied warranty of
015 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
016 * GNU General Lesser Public License for more details.
017 * 
018 * You should have received a copy of the GNU General Lesser Public
019 * License along with this program.  If not, see
020 * <http://www.gnu.org/licenses/lgpl-2.1.html>.
021 * #L%
022 */
023
024package dk.netarkivet.heritrix3.monitor;
025
026import java.util.HashMap;
027import java.util.Iterator;
028import java.util.LinkedHashMap;
029import java.util.List;
030import java.util.Locale;
031import java.util.Map;
032
033import javax.servlet.http.Cookie;
034import javax.servlet.http.HttpServletRequest;
035import javax.servlet.http.HttpServletResponse;
036
037import dk.netarkivet.common.CommonSettings;
038import dk.netarkivet.common.utils.Settings;
039import dk.netarkivet.common.utils.StringTree;
040import dk.netarkivet.heritrix3.monitor.AcceptLanguageParser.AcceptLanguage;
041
042/**
043 * A class used to determine the appropriate locale/language to use for generating a HTTP response.
044 * Uses the HTTP request "Accept-Language" header, locale cookie or default locale.
045 * Also caches a structure of all available locale combinations in the execution environment.
046 */
047public class HttpLocaleHandler {
048
049        /** <code>Map</code> of all Locale objects registered by the execution environment. */
050    public final Map<String, Locale> localeMap = new HashMap<String, Locale>();
051
052    /** Structure including the */
053    public final LinkedHashMap<String, Language> languageLHM = new LinkedHashMap<String, Language>();
054
055    /**
056     * Object constructed from language configuration in the settings xml file.
057     */
058    public static class Language {
059        /** Locate string identifier. */
060        public String language;
061        /* Localized language description. */
062        public String language_name;
063        /** Execution environment <code>Locale</code> object retrieved from the identifier. */
064        public Locale locale;
065    }
066
067    /**
068     * HTTP locale object with information about the best matched locale based on Accept-Language header,
069     * cookie usage and the NAS language object.
070     */
071    public class HttpLocale {
072        /** Did the HTTP request include a locale cookie. */
073        public boolean bCookie;
074        /** Language object based on NAS language configuration. */
075        public Language languageObj;
076        /** Best match locale object. */
077        public Locale locale;
078
079        /**
080         * Generate language selection HTML based on the matched locale and cookie usage.
081         * @return generated language HTML
082         */
083        public String generateLanguageLinks() {
084            String languageStr = languageObj.locale.getLanguage();
085            StringBuilder sb = new StringBuilder();
086            sb.append("<div class=\"languagelinks\">");
087            Iterator<Language> languageIter = languageLHM.values().iterator();
088            Language language;
089            while (languageIter.hasNext()) {
090                language = languageIter.next();
091                if (languageStr.equalsIgnoreCase(language.language)) {
092                    sb.append("<a href=\"");
093                    if (bCookie) {
094                        sb.append("#");
095                    }
096                    else {
097                        sb.append("?locale=");
098                        sb.append(language.language);
099                    }
100                    sb.append("\">");
101                    sb.append("<b>");
102                    sb.append(language.language_name);
103                    sb.append("</b>");
104                    sb.append("</a>");
105                    sb.append("&nbsp;");
106                } else {
107                    sb.append("<a href=\"");
108                    sb.append("?locale=");
109                    sb.append(language.language);
110                    sb.append("\">");
111                    sb.append(language.language_name);
112                    sb.append("</a>&nbsp;");
113                }
114            }
115            if (bCookie) {
116                sb.append("<a href=\"?locale=");
117                sb.append("\">");
118                sb.append("(Remove cookie)");
119                sb.append("</a>&nbsp;");
120            }
121            sb.append("</div>");
122            return sb.toString();
123        }
124    }
125
126    /**
127     * Constructor should only be used locally or in unit test.
128     */
129    protected HttpLocaleHandler() {
130    }
131
132    /**
133     * Construct a HTTP locale handler. Reads all available locates in the execution environment
134     * and in the NAS settings XML.
135     * @return initialised HTTP locale handler
136     */
137    public static HttpLocaleHandler getInstance() {
138        HttpLocaleHandler httpLocaleHandler = new HttpLocaleHandler();
139        Locale[] locales = Locale.getAvailableLocales();
140        Locale locale;
141        String languageStr;
142        String countryStr;
143        for ( int i=0; i<locales.length; ++i ) {
144            locale = locales[ i ];
145            languageStr = locale.getLanguage();
146            countryStr = locale.getCountry();
147            if (countryStr != null) {
148                httpLocaleHandler.localeMap.put(languageStr + '-' + countryStr, locale);
149                if (!httpLocaleHandler.localeMap.containsKey(languageStr)) {
150                    httpLocaleHandler.localeMap.put(languageStr, locale);
151                }
152            }
153            else {
154                httpLocaleHandler.localeMap.put(languageStr, locale);
155            }
156            httpLocaleHandler.localeMap.put(locale.getLanguage(), locale);
157        }
158
159        StringTree<String> webinterfaceSettings = Settings.getTree(CommonSettings.WEBINTERFACE_SETTINGS);
160        Language languageObj;
161        for (StringTree<String> languageSetting : webinterfaceSettings.getSubTrees(CommonSettings.WEBINTERFACE_LANGUAGE)) {
162            languageObj = new Language();
163            languageObj.language = languageSetting.getValue(CommonSettings.WEBINTERFACE_LANGUAGE_LOCALE);
164            languageObj.language_name = languageSetting.getValue(CommonSettings.WEBINTERFACE_LANGUAGE_NAME);
165            languageObj.locale = httpLocaleHandler.localeMap.get(languageObj.language);
166            httpLocaleHandler.languageLHM.put(languageObj.language, languageObj);
167        }
168
169        return httpLocaleHandler;
170    }
171
172    /**
173     * Determine the closest locale that matches the information in the HTTP request.
174     * Depending on usage it manages a cookie.
175     * If no cookie exists reads/parses the HTTP request "Accept-Language" header. 
176     * 
177     * @param req HTTP request object
178     * @param resp HTTP response object
179     * @return <code>HttpLocale</code> object determined to be the closest match to what the request wants
180     */
181    public HttpLocale localeGetSet(HttpServletRequest req, HttpServletResponse resp) {
182        HttpLocale httpLocale = new HttpLocale();
183        boolean bCookieDeleted = false;
184        // Request parameter.
185        String languageStr = req.getParameter("locale");
186        Language languageObj = null;
187        if (languageStr != null) {
188            if (languageStr.length() > 0) {
189                languageObj = languageLHM.get(languageStr);
190                if (languageObj != null) {
191                    Cookie cookie = new Cookie("locale", languageStr);
192                    cookie.setPath("/");
193                    //Keep the cookie for a year
194                    cookie.setMaxAge(365 * 24 * 60 * 60);
195                    resp.addCookie(cookie);
196                    httpLocale.bCookie = true;
197                }
198            }
199            else {
200                Cookie cookie = new Cookie("locale", languageStr);
201                cookie.setPath("/");
202                cookie.setMaxAge(0);
203                resp.addCookie(cookie);
204                bCookieDeleted = true;
205            }
206        }
207        // Cookie.
208        if (languageObj == null && !bCookieDeleted) {
209            Cookie[] cookies = req.getCookies();
210            if (cookies != null) {
211                for (Cookie c : cookies) {
212                    if (c.getName().equals("locale")) {
213                        languageStr = c.getValue();
214                        languageObj = languageLHM.get(languageStr);
215                        httpLocale.bCookie = true;
216                    }
217                }
218            }
219        }
220        // Accept-Language.
221        if (languageObj == null) {
222            List<AcceptLanguage> acceptLanguages = AcceptLanguageParser.parseHeader(req);
223            AcceptLanguage acceptLanguage;
224            int idx = 0;
225            int len = acceptLanguages.size();
226            while (idx < len && languageObj == null) {
227                acceptLanguage = acceptLanguages.get(idx);
228                languageObj = languageLHM.get(acceptLanguage.locale);
229                if (languageObj == null) {
230                    languageObj = languageLHM.get(acceptLanguage.language);
231                }
232                ++idx;
233            }
234        }
235        // Fall back to default.
236        if (languageObj == null) {
237            languageObj = languageLHM.get("en");
238        }
239        // Locale.
240        httpLocale.languageObj = languageObj;
241        if (languageObj != null) {
242            httpLocale.locale = languageObj.locale;
243            resp.setLocale(httpLocale.locale);
244        }
245        else {
246            httpLocale.locale = resp.getLocale();
247        }
248        return httpLocale;
249    }
250
251}