/* * Copyright (c) 2002-2012 Alibaba Group Holding Limited. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.alibaba.citrus.service.requestcontext.locale.impl; import static com.alibaba.citrus.util.ServletUtil.*; import static com.alibaba.citrus.util.StringUtil.*; import java.io.UnsupportedEncodingException; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import com.alibaba.citrus.service.requestcontext.RequestContext; import com.alibaba.citrus.service.requestcontext.locale.SetLocaleRequestContext; import com.alibaba.citrus.service.requestcontext.support.AbstractRequestContextWrapper; import com.alibaba.citrus.service.requestcontext.support.AbstractRequestWrapper; import com.alibaba.citrus.service.requestcontext.support.AbstractResponseWrapper; import com.alibaba.citrus.util.i18n.LocaleInfo; import com.alibaba.citrus.util.i18n.LocaleUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>SetLocaleRequestContext</code>的实现。 * * @author Michael Zhou */ public class SetLocaleRequestContextImpl extends AbstractRequestContextWrapper implements SetLocaleRequestContext { private final static Logger log = LoggerFactory.getLogger(SetLocaleRequestContext.class); private Pattern inputCharsetPattern; private Pattern outputCharsetPattern; private SetLocaleOverrider[] overriders; private Locale defaultLocale; private String defaultCharset; private String sessionKey; private String paramKey; private Locale locale; /** * 包装一个<code>RequestContext</code>对象。 * * @param wrappedContext 被包装的<code>RequestContext</code> */ public SetLocaleRequestContextImpl(RequestContext wrappedContext) { super(wrappedContext); setRequest(new RequestWrapper(wrappedContext.getRequest())); setResponse(new ResponseWrapper(wrappedContext.getResponse())); } public void setInputCharsetPattern(Pattern inputCharsetPattern) { this.inputCharsetPattern = inputCharsetPattern; } public void setOutputCharsetPattern(Pattern outputCharsetPattern) { this.outputCharsetPattern = outputCharsetPattern; } public void setOverriders(SetLocaleOverrider[] overriders) { this.overriders = overriders; } public void setDefaultLocale(Locale defaultLocale) { this.defaultLocale = defaultLocale; } public void setDefaultCharset(String defaultCharset) { this.defaultCharset = defaultCharset; } public void setSessionKey(String sessionKey) { this.sessionKey = sessionKey; } public void setParamKey(String paramKey) { this.paramKey = paramKey; } /** * 取得content type。 * * @return content type,包括charset的定义 */ public String getResponseContentType() { return ((ResponseWrapper) getResponse()).getContentType(); } /** * 设置content type。 如果content type不包含charset,并且 * <code>getCharacterEncoding</code>被设置,则加上charset标记。 * <p> * 如果<code>appendCharset</code>为<code>false</code>,则content * type中将不包含charset标记。 * </p> * * @param contentType content type * @param appendCharset 输出字符集 */ public void setResponseContentType(String contentType, boolean appendCharset) { ((ResponseWrapper) getResponse()).setContentType(contentType, appendCharset); } /** * 设置response输出字符集。注意,此方法必须在第一次<code>getWriter</code>之前执行。 * * @param charset 输出字符集,如果charset为<code>null</code> * ,则从contentType中删除charset标记 */ public void setResponseCharacterEncoding(String charset) { ((ResponseWrapper) getResponse()).setCharacterEncoding(charset); } /** 设置locale、输入charset、输出charset。 */ @Override public void prepare() { // 首先从session中取得localeInfo,如果不存在,则取得默认值。 LocaleInfo localeInfo = getLocaleInfoFromSessionOrGetTheDefaultValue(); // 匹配request uri SetLocaleOverrider overrider = getMatchedOverrider(); // 将input charset设置到request中,以便进一步解析request parameters。 setInputCharsetToRequest(localeInfo.getCharset().name(), overrider); // 现在已经可以安全地调用getParameter()方法,以解析参数了,因为input charset已经被设置。 // 从parameter中取locale信息,如果存在,则设置到cookie中。 if (PARAMETER_SET_TO_DEFAULT_VALUE.equalsIgnoreCase(getRequest().getParameter(paramKey))) { localeInfo = resetLocaleInfoInSession(); } else { String outputCharset = getOutputCharsetFromQueryString(); if (outputCharset == null) { outputCharset = getOutputCharsetOverridden(overrider); } // 如果parameter中指明了locale,则取得并保存之 LocaleInfo paramLocale = getLocaleInfoFromParameter(); if (paramLocale != null) { getRequest().getSession().setAttribute(sessionKey, paramLocale.toString()); // 用parameter中的locale信息覆盖session中的localeInfo信息。 localeInfo = paramLocale; } if (outputCharset != null) { localeInfo = new LocaleInfo(localeInfo.getLocale(), outputCharset); } } // 设置用于输出的locale信息。 getResponse().setLocale(localeInfo.getLocale()); setResponseCharacterEncoding(localeInfo.getCharset().name()); log.debug("Set OUTPUT locale:charset to " + localeInfo); // 设置thread context中的locale信息。 LocaleUtil.setContext(localeInfo.getLocale(), localeInfo.getCharset().name()); log.debug("Set THREAD CONTEXT locale:charset to " + localeInfo); this.locale = localeInfo.getLocale(); } /** 找出request uri匹配的overrider。 */ private SetLocaleOverrider getMatchedOverrider() { if (overriders != null && overriders.length > 0) { String path = getResourcePath(getRequest()); for (SetLocaleOverrider overrider : overriders) { if (overrider.getRequestUriPattern().matcher(path).find()) { return overrider; } } } return null; } /** * 从当前请求的session中取得用户的locale设置。如果session未设置,则取默认值。 * * @return 当前session中的locale设置或默认值 */ private LocaleInfo getLocaleInfoFromSessionOrGetTheDefaultValue() { HttpSession session = getRequest().getSession(false); // 如果session不存在,也不用创建。 String localeName = session == null ? null : (String) getRequest().getSession().getAttribute(sessionKey); LocaleInfo localeInfo; if (isEmpty(localeName)) { localeInfo = new LocaleInfo(defaultLocale, defaultCharset); } else { localeInfo = LocaleInfo.parse(localeName); if (!LocaleUtil.isLocaleSupported(localeInfo.getLocale()) || !LocaleUtil.isCharsetSupported(localeInfo.getCharset().name())) { log.warn("Invalid locale " + localeInfo + " from session"); localeInfo = new LocaleInfo(defaultLocale, defaultCharset); } } return localeInfo; } /** * 设置input charset。 * 假如query string中指定了input charset,则采用之。 * 否则使用参数所指定的值作为input charset。 * * @param inputCharset 默认input charset,或是从session中所取得的input charset * @param overrider 如果有,则从中取得input charset */ private void setInputCharsetToRequest(String inputCharset, SetLocaleOverrider overrider) { try { String charset = getInputCharsetFromQueryString(); if (charset == null) { charset = getInputCharsetOverridden(overrider); } if (charset != null) { inputCharset = charset; } getRequest().setCharacterEncoding(inputCharset); log.debug("Set INPUT charset to " + inputCharset); } catch (UnsupportedEncodingException e) { try { getRequest().setCharacterEncoding(CHARSET_DEFAULT); log.warn("Unknown charset {}. Set INPUT charset to {}", inputCharset, CHARSET_DEFAULT); } catch (UnsupportedEncodingException ee) { log.error("Failed to set INPUT charset to {}", CHARSET_DEFAULT); } } } /** 试图从queryString中取得inputCharset。 */ private String getInputCharsetFromQueryString() { String inputCharsetQS = null; String queryString = getRequest().getQueryString(); if (queryString != null) { Matcher matcher = inputCharsetPattern.matcher(queryString); if (matcher.find()) { String charset = null; if (matcher.groupCount() >= 2) { charset = matcher.group(2); } if (LocaleUtil.isCharsetSupported(charset)) { inputCharsetQS = charset; } else { log.warn("Specified input charset is not supported: " + charset); } } } return inputCharsetQS; } private String getInputCharsetOverridden(SetLocaleOverrider overrider) { String inputCharsetOverridden = null; if (overrider != null) { String charset = overrider.getInputCharset(); if (charset != null) { if (LocaleUtil.isCharsetSupported(charset)) { inputCharsetOverridden = charset; } else { log.warn("Specified overridden input charset is not supported: " + charset); } } } return inputCharsetOverridden; } /** * 恢复session中所保存的localeInfo信息。 * * @return 默认的localeInfo */ private LocaleInfo resetLocaleInfoInSession() { HttpSession session = getRequest().getSession(false); // 如果session不存在,也不用创建 if (session != null) { session.removeAttribute(sessionKey); } LocaleInfo localeInfo = new LocaleInfo(defaultLocale, defaultCharset); log.debug("Reset OUTPUT locale:charset to " + localeInfo); return localeInfo; } /** 试图从queryString中取得outputCharset。 */ private String getOutputCharsetFromQueryString() { String queryString = getRequest().getQueryString(); String outputCharsetQS = null; if (queryString != null) { Matcher matcher = outputCharsetPattern.matcher(queryString); if (matcher.find()) { String charset = null; if (matcher.groupCount() >= 2) { charset = matcher.group(2); } if (LocaleUtil.isCharsetSupported(charset)) { outputCharsetQS = charset; } else { log.warn("Specified output charset is not supported: " + charset); } } } return outputCharsetQS; } private String getOutputCharsetOverridden(SetLocaleOverrider overrider) { String outputCharsetOverridden = null; if (overrider != null) { String charset = overrider.getOutputCharset(); if (charset != null) { if (LocaleUtil.isCharsetSupported(charset)) { outputCharsetOverridden = charset; } else { log.warn("Specified overridden output charset is not supported: " + charset); } } } return outputCharsetOverridden; } /** * 从当前请求的参数中取得用户的locale设置。如果参数未设置,则返回<code>null</code>。 * * @return 当前request parameters中的locale设置 */ private LocaleInfo getLocaleInfoFromParameter() { String localeName = getRequest().getParameter(paramKey); LocaleInfo localeInfo = null; if (!isEmpty(localeName)) { localeInfo = LocaleInfo.parse(localeName); if (!LocaleUtil.isLocaleSupported(localeInfo.getLocale()) || !LocaleUtil.isCharsetSupported(localeInfo.getCharset().name())) { log.warn("Invalid locale " + localeInfo + " from request parameters"); localeInfo = new LocaleInfo(defaultLocale, defaultCharset); } } return localeInfo; } /** 包装request。 */ private class RequestWrapper extends AbstractRequestWrapper { public RequestWrapper(HttpServletRequest request) { super(SetLocaleRequestContextImpl.this, request); } @Override public Locale getLocale() { return locale == null ? super.getLocale() : locale; } } /** 包装response。 */ private class ResponseWrapper extends AbstractResponseWrapper { private String contentType; private String charset; public ResponseWrapper(HttpServletResponse response) { super(SetLocaleRequestContextImpl.this, response); } /** * 取得content type。 * * @return content type,包括charset的定义 */ @Override public String getContentType() { return contentType; } /** * 设置content type。 如果content type不包含charset,并且 * <code>getCharacterEncoding</code>被设置,则加上charset标记。 * * @param contentType content type */ @Override public void setContentType(String contentType) { setContentType(contentType, true); } /** * 设置content type。 如果content type不包含charset,并且 * <code>getCharacterEncoding</code>被设置,则加上charset标记。 * <p> * 如果<code>appendCharset</code>为<code>false</code>,则content * type中将不包含charset标记。 * </p> * * @param contentType content type * @param appendCharset 输出字符集 */ public void setContentType(String contentType, boolean appendCharset) { // 取得指定contentType中的"; charset="部分。 String charset = trimToNull(substringAfterLast(contentType, "charset=")); // 如果未指定charset,则从this.charset中取,这是由setCharacterEncoding方法所设置的。 if (charset == null) { charset = this.charset; } // 除去contentType中的charset部分。 this.contentType = trimToNull(substringBefore(contentType, ";")); // 调用setCharacterEncoding方法加上charset。 setCharacterEncoding(appendCharset ? charset : null); } /** 取得response的输出字符集。 */ @Override public String getCharacterEncoding() { return super.getCharacterEncoding(); } /** * 设置response输出字符集。注意,此方法必须在第一次<code>getWriter</code>之前执行。 * * @param charset 输出字符集,如果charset为<code>null</code> * ,则从contentType中删除charset标记 */ @Override public void setCharacterEncoding(String charset) { this.charset = charset; if (contentType != null) { contentType = trimToNull(substringBefore(contentType, ";")); if (charset != null) { contentType += "; charset=" + charset; } log.debug("Set content type to " + contentType); super.setContentType(contentType); } else { // 假如没有设置contentType,确保charset仍然被设置。 // 适用于Servlet API 2.4及更新版。 try { super.setCharacterEncoding(charset); } catch (NoSuchMethodError e) { } } } } }