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(" "); 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> "); 113 } 114 } 115 if (bCookie) { 116 sb.append("<a href=\"?locale="); 117 sb.append("\">"); 118 sb.append("(Remove cookie)"); 119 sb.append("</a> "); 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}