001/* 002 * Copyright 2002-2019 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.Locale; 020 021import javax.servlet.ServletException; 022import javax.servlet.http.HttpServletRequest; 023import javax.servlet.http.HttpServletResponse; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027 028import org.springframework.lang.Nullable; 029import org.springframework.util.ObjectUtils; 030import org.springframework.util.StringUtils; 031import org.springframework.web.servlet.LocaleResolver; 032import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; 033import org.springframework.web.servlet.support.RequestContextUtils; 034 035/** 036 * Interceptor that allows for changing the current locale on every request, 037 * via a configurable request parameter (default parameter name: "locale"). 038 * 039 * @author Juergen Hoeller 040 * @author Rossen Stoyanchev 041 * @since 20.06.2003 042 * @see org.springframework.web.servlet.LocaleResolver 043 */ 044public class LocaleChangeInterceptor extends HandlerInterceptorAdapter { 045 046 /** 047 * Default name of the locale specification parameter: "locale". 048 */ 049 public static final String DEFAULT_PARAM_NAME = "locale"; 050 051 052 protected final Log logger = LogFactory.getLog(getClass()); 053 054 private String paramName = DEFAULT_PARAM_NAME; 055 056 @Nullable 057 private String[] httpMethods; 058 059 private boolean ignoreInvalidLocale = false; 060 061 062 /** 063 * Set the name of the parameter that contains a locale specification 064 * in a locale change request. Default is "locale". 065 */ 066 public void setParamName(String paramName) { 067 this.paramName = paramName; 068 } 069 070 /** 071 * Return the name of the parameter that contains a locale specification 072 * in a locale change request. 073 */ 074 public String getParamName() { 075 return this.paramName; 076 } 077 078 /** 079 * Configure the HTTP method(s) over which the locale can be changed. 080 * @param httpMethods the methods 081 * @since 4.2 082 */ 083 public void setHttpMethods(@Nullable String... httpMethods) { 084 this.httpMethods = httpMethods; 085 } 086 087 /** 088 * Return the configured HTTP methods. 089 * @since 4.2 090 */ 091 @Nullable 092 public String[] getHttpMethods() { 093 return this.httpMethods; 094 } 095 096 /** 097 * Set whether to ignore an invalid value for the locale parameter. 098 * @since 4.2.2 099 */ 100 public void setIgnoreInvalidLocale(boolean ignoreInvalidLocale) { 101 this.ignoreInvalidLocale = ignoreInvalidLocale; 102 } 103 104 /** 105 * Return whether to ignore an invalid value for the locale parameter. 106 * @since 4.2.2 107 */ 108 public boolean isIgnoreInvalidLocale() { 109 return this.ignoreInvalidLocale; 110 } 111 112 /** 113 * Specify whether to parse request parameter values as BCP 47 language tags 114 * instead of Java's legacy locale specification format. 115 * <p><b>NOTE: As of 5.1, this resolver leniently accepts the legacy 116 * {@link Locale#toString} format as well as BCP 47 language tags.</b> 117 * @since 4.3 118 * @see Locale#forLanguageTag(String) 119 * @see Locale#toLanguageTag() 120 * @deprecated as of 5.1 since it only accepts {@code true} now 121 */ 122 @Deprecated 123 public void setLanguageTagCompliant(boolean languageTagCompliant) { 124 if (!languageTagCompliant) { 125 throw new IllegalArgumentException("LocaleChangeInterceptor always accepts BCP 47 language tags"); 126 } 127 } 128 129 /** 130 * Return whether to use BCP 47 language tags instead of Java's legacy 131 * locale specification format. 132 * @since 4.3 133 * @deprecated as of 5.1 since it always returns {@code true} now 134 */ 135 @Deprecated 136 public boolean isLanguageTagCompliant() { 137 return true; 138 } 139 140 141 @Override 142 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 143 throws ServletException { 144 145 String newLocale = request.getParameter(getParamName()); 146 if (newLocale != null) { 147 if (checkHttpMethod(request.getMethod())) { 148 LocaleResolver localeResolver = RequestContextUtils.getLocaleResolver(request); 149 if (localeResolver == null) { 150 throw new IllegalStateException( 151 "No LocaleResolver found: not in a DispatcherServlet request?"); 152 } 153 try { 154 localeResolver.setLocale(request, response, parseLocaleValue(newLocale)); 155 } 156 catch (IllegalArgumentException ex) { 157 if (isIgnoreInvalidLocale()) { 158 if (logger.isDebugEnabled()) { 159 logger.debug("Ignoring invalid locale value [" + newLocale + "]: " + ex.getMessage()); 160 } 161 } 162 else { 163 throw ex; 164 } 165 } 166 } 167 } 168 // Proceed in any case. 169 return true; 170 } 171 172 private boolean checkHttpMethod(String currentMethod) { 173 String[] configuredMethods = getHttpMethods(); 174 if (ObjectUtils.isEmpty(configuredMethods)) { 175 return true; 176 } 177 for (String configuredMethod : configuredMethods) { 178 if (configuredMethod.equalsIgnoreCase(currentMethod)) { 179 return true; 180 } 181 } 182 return false; 183 } 184 185 /** 186 * Parse the given locale value as coming from a request parameter. 187 * <p>The default implementation calls {@link StringUtils#parseLocale(String)}, 188 * accepting the {@link Locale#toString} format as well as BCP 47 language tags. 189 * @param localeValue the locale value to parse 190 * @return the corresponding {@code Locale} instance 191 * @since 4.3 192 */ 193 @Nullable 194 protected Locale parseLocaleValue(String localeValue) { 195 return StringUtils.parseLocale(localeValue); 196 } 197 198}