001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 * 027 * This file is based on: 028 * org.apache.fulcrum.localization.LocaleTokenizer 029 * from the Apache Fulcrum/Turbine project. 030 * 031 * The Apache Software License, Version 1.1 032 * 033 * Copyright (c) 2001 The Apache Software Foundation. All rights 034 * reserved. 035 * 036 * Redistribution and use in source and binary forms, with or without 037 * modification, are permitted provided that the following conditions 038 * are met: 039 * 040 * 1. Redistributions of source code must retain the above copyright 041 * notice, this list of conditions and the following disclaimer. 042 * 043 * 2. Redistributions in binary form must reproduce the above copyright 044 * notice, this list of conditions and the following disclaimer in 045 * the documentation and/or other materials provided with the 046 * distribution. 047 * 048 * 3. The end-user documentation included with the redistribution, 049 * if any, must include the following acknowledgment: 050 * "This product includes software developed by the 051 * Apache Software Foundation (http://www.apache.org/)." 052 * Alternately, this acknowledgment may appear in the software itself, 053 * if and wherever such third-party acknowledgments normally appear. 054 * 055 * 4. The names "Apache" and "Apache Software Foundation" and 056 * "Apache Turbine" must not be used to endorse or promote products 057 * derived from this software without prior written permission. For 058 * written permission, please contact apache@apache.org. 059 * 060 * 5. Products derived from this software may not be called "Apache", 061 * "Apache Turbine", nor may "Apache" appear in their name, without 062 * prior written permission of the Apache Software Foundation. 063 * 064 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 065 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 066 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 067 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 068 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 069 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 070 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 071 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 072 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 073 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 074 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 075 * SUCH DAMAGE. 076 * ==================================================================== 077 * 078 * This software consists of voluntary contributions made by many 079 * individuals on behalf of the Apache Software Foundation. For more 080 * information on the Apache Software Foundation, please see 081 * <http://www.apache.org/>. 082 */ 083 084package org.opencms.i18n; 085 086import org.opencms.main.OpenCms; 087import org.opencms.util.CmsStringUtil; 088 089import java.util.ArrayList; 090import java.util.Collections; 091import java.util.Iterator; 092import java.util.List; 093import java.util.Locale; 094 095import javax.servlet.http.HttpServletRequest; 096 097/** 098 * Parses the HTTP <code>Accept-Language</code> header as per section 14.4 of RFC 2068 099 * (HTTP 1.1 header field definitions) and creates a sorted list of Locales from it. 100 * 101 * @since 6.0.0 102 */ 103public class CmsAcceptLanguageHeaderParser { 104 105 /** 106 * Struct representing an element of the HTTP <code>Accept-Language</code> header. 107 */ 108 protected static class AcceptLanguage implements Comparable<AcceptLanguage> { 109 110 /** The language and country. */ 111 Locale m_locale; 112 113 /** The m_quality of our m_locale (as values approach <code>1.0</code>, they indicate increased user preference). */ 114 Float m_quality = DEFAULT_QUALITY; 115 116 /** 117 * @see java.lang.Comparable#compareTo(java.lang.Object) 118 */ 119 public final int compareTo(AcceptLanguage acceptLang) { 120 121 return m_quality.compareTo((acceptLang).m_quality); 122 } 123 124 /** 125 * @see java.lang.Object#equals(java.lang.Object) 126 */ 127 @Override 128 public boolean equals(Object obj) { 129 130 if (obj == this) { 131 return true; 132 } 133 if (obj instanceof AcceptLanguage) { 134 AcceptLanguage other = (AcceptLanguage)obj; 135 return m_locale.equals(other.m_locale) && (m_quality.floatValue() == other.m_quality.floatValue()); 136 } 137 return false; 138 } 139 140 /** 141 * @see java.lang.Object#hashCode() 142 */ 143 @Override 144 public int hashCode() { 145 146 return m_locale.hashCode() * (int)(m_quality.floatValue() * 1117.0); 147 } 148 } 149 150 /** A constant for the HTTP <code>Accept-Language</code> header. */ 151 public static final String ACCEPT_LANGUAGE = "Accept-Language"; 152 153 /** The default m_quality value for an <code>AcceptLanguage</code> object. */ 154 protected static final Float DEFAULT_QUALITY = Float.valueOf(1.0f); 155 156 /** Separates elements of the <code>Accept-Language</code> HTTP header. */ 157 private static final char LOCALE_SEPARATOR = ','; 158 159 /** Separates m_locale from m_quality within elements. */ 160 private static final char QUALITY_SEPARATOR = ';'; 161 162 /** The parsed <code>Accept-Language</code> headers. */ 163 private List<AcceptLanguage> m_acceptLanguage = new ArrayList<AcceptLanguage>(3); 164 165 /** The parsed locales. */ 166 private List<Locale> m_locales; 167 168 /** 169 * Parses the <code>Accept-Language</code> header from the provided request.<p> 170 * 171 * @param req the request to parse 172 * @param defaultLocale the default locale to use 173 */ 174 public CmsAcceptLanguageHeaderParser(HttpServletRequest req, Locale defaultLocale) { 175 176 this(req.getHeader(ACCEPT_LANGUAGE), defaultLocale); 177 } 178 179 /** 180 * Parses the <code>Accept-Language</code> header.<p> 181 * 182 * @param header the <code>Accept-Language</code> header (i.e. <code>en, es;q=0.8, zh-TW;q=0.1</code>) 183 * @param defaultLocale the default locale to use 184 */ 185 public CmsAcceptLanguageHeaderParser(String header, Locale defaultLocale) { 186 187 // check if there was a locale foud in the HTTP header. 188 // if not, use the default locale. 189 if (header == null) { 190 m_locales = new ArrayList<Locale>(); 191 m_locales.add(defaultLocale); 192 } else { 193 List<String> tokens = CmsStringUtil.splitAsList(header, LOCALE_SEPARATOR, true); 194 Iterator<String> it = tokens.iterator(); 195 while (it.hasNext()) { 196 AcceptLanguage acceptLang = new AcceptLanguage(); 197 String element = it.next(); 198 int index; 199 200 // Record and cut off any quality value that comes after a semi-colon. 201 index = element.indexOf(QUALITY_SEPARATOR); 202 if (index != -1) { 203 String q = element.substring(index); 204 element = element.substring(0, index); 205 index = q.indexOf('='); 206 if (index != -1) { 207 try { 208 acceptLang.m_quality = Float.valueOf(q.substring(index + 1)); 209 } catch (NumberFormatException useDefault) { 210 // noop 211 } 212 } 213 } 214 215 element = element.trim(); 216 217 // Create a Locale from the language. A dash may separate the language from the country. 218 index = element.indexOf('-'); 219 if (index == -1) { 220 // No dash means no country. 221 acceptLang.m_locale = new Locale(element, ""); 222 } else { 223 acceptLang.m_locale = new Locale(element.substring(0, index), element.substring(index + 1)); 224 } 225 226 m_acceptLanguage.add(acceptLang); 227 } 228 229 // sort by quality in descending order 230 Collections.sort(m_acceptLanguage, Collections.reverseOrder()); 231 232 // store all calculated Locales in a List 233 m_locales = new ArrayList<Locale>(m_acceptLanguage.size()); 234 Iterator<AcceptLanguage> i = m_acceptLanguage.iterator(); 235 while (i.hasNext()) { 236 AcceptLanguage lang = i.next(); 237 m_locales.add(lang.m_locale); 238 } 239 } 240 241 } 242 243 /** 244 * Creates a value string for the HTTP Accept-Language header based on the default localed.<p> 245 * 246 * @return value string for the HTTP Accept-Language 247 */ 248 public static String createLanguageHeader() { 249 250 String header; 251 252 // get the default accept-language header value 253 List<Locale> defaultLocales = OpenCms.getLocaleManager().getDefaultLocales(); 254 Iterator<Locale> i = defaultLocales.iterator(); 255 header = ""; 256 while (i.hasNext()) { 257 Locale loc = i.next(); 258 header += loc.getLanguage() + ", "; 259 } 260 header = header.substring(0, header.length() - 2); 261 return header; 262 } 263 264 /** 265 * Returns the sorted list of accepted Locales.<p> 266 * 267 * @return the sorted list of accepted Locales 268 */ 269 public List<Locale> getAcceptedLocales() { 270 271 return m_locales; 272 } 273}