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 = new Float(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}