001/*
002 * Copyright 2002-2020 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.web.servlet.i18n;
018
019import java.util.ArrayList;
020import java.util.Enumeration;
021import java.util.List;
022import java.util.Locale;
023
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026
027import org.springframework.lang.Nullable;
028import org.springframework.util.StringUtils;
029import org.springframework.web.servlet.LocaleResolver;
030
031/**
032 * {@link LocaleResolver} implementation that simply uses the primary locale
033 * specified in the "accept-language" header of the HTTP request (that is,
034 * the locale sent by the client browser, normally that of the client's OS).
035 *
036 * <p>Note: Does not support {@code setLocale}, since the accept header
037 * can only be changed through changing the client's locale settings.
038 *
039 * @author Juergen Hoeller
040 * @author Rossen Stoyanchev
041 * @since 27.02.2003
042 * @see javax.servlet.http.HttpServletRequest#getLocale()
043 */
044public class AcceptHeaderLocaleResolver implements LocaleResolver {
045
046        private final List<Locale> supportedLocales = new ArrayList<>(4);
047
048        @Nullable
049        private Locale defaultLocale;
050
051
052        /**
053         * Configure supported locales to check against the requested locales
054         * determined via {@link HttpServletRequest#getLocales()}. If this is not
055         * configured then {@link HttpServletRequest#getLocale()} is used instead.
056         * @param locales the supported locales
057         * @since 4.3
058         */
059        public void setSupportedLocales(List<Locale> locales) {
060                this.supportedLocales.clear();
061                this.supportedLocales.addAll(locales);
062        }
063
064        /**
065         * Return the configured list of supported locales.
066         * @since 4.3
067         */
068        public List<Locale> getSupportedLocales() {
069                return this.supportedLocales;
070        }
071
072        /**
073         * Configure a fixed default locale to fall back on if the request does not
074         * have an "Accept-Language" header.
075         * <p>By default this is not set in which case when there is "Accept-Language"
076         * header, the default locale for the server is used as defined in
077         * {@link HttpServletRequest#getLocale()}.
078         * @param defaultLocale the default locale to use
079         * @since 4.3
080         */
081        public void setDefaultLocale(@Nullable Locale defaultLocale) {
082                this.defaultLocale = defaultLocale;
083        }
084
085        /**
086         * The configured default locale, if any.
087         * <p>This method may be overridden in subclasses.
088         * @since 4.3
089         */
090        @Nullable
091        public Locale getDefaultLocale() {
092                return this.defaultLocale;
093        }
094
095
096        @Override
097        public Locale resolveLocale(HttpServletRequest request) {
098                Locale defaultLocale = getDefaultLocale();
099                if (defaultLocale != null && request.getHeader("Accept-Language") == null) {
100                        return defaultLocale;
101                }
102                Locale requestLocale = request.getLocale();
103                List<Locale> supportedLocales = getSupportedLocales();
104                if (supportedLocales.isEmpty() || supportedLocales.contains(requestLocale)) {
105                        return requestLocale;
106                }
107                Locale supportedLocale = findSupportedLocale(request, supportedLocales);
108                if (supportedLocale != null) {
109                        return supportedLocale;
110                }
111                return (defaultLocale != null ? defaultLocale : requestLocale);
112        }
113
114        @Nullable
115        private Locale findSupportedLocale(HttpServletRequest request, List<Locale> supportedLocales) {
116                Enumeration<Locale> requestLocales = request.getLocales();
117                Locale languageMatch = null;
118                while (requestLocales.hasMoreElements()) {
119                        Locale locale = requestLocales.nextElement();
120                        if (supportedLocales.contains(locale)) {
121                                if (languageMatch == null || languageMatch.getLanguage().equals(locale.getLanguage())) {
122                                        // Full match: language + country, possibly narrowed from earlier language-only match
123                                        return locale;
124                                }
125                        }
126                        else if (languageMatch == null) {
127                                // Let's try to find a language-only match as a fallback
128                                for (Locale candidate : supportedLocales) {
129                                        if (!StringUtils.hasLength(candidate.getCountry()) &&
130                                                        candidate.getLanguage().equals(locale.getLanguage())) {
131                                                languageMatch = candidate;
132                                                break;
133                                        }
134                                }
135                        }
136                }
137                return languageMatch;
138        }
139
140        @Override
141        public void setLocale(HttpServletRequest request, @Nullable HttpServletResponse response, @Nullable Locale locale) {
142                throw new UnsupportedOperationException(
143                                "Cannot change HTTP accept header - use a different locale resolution strategy");
144        }
145
146}