001/*
002 * Copyright 2002-2017 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.support;
018
019import java.util.Locale;
020import java.util.ResourceBundle;
021import java.util.TimeZone;
022
023import javax.servlet.ServletContext;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpSession;
026import javax.servlet.jsp.jstl.core.Config;
027import javax.servlet.jsp.jstl.fmt.LocalizationContext;
028
029import org.springframework.context.MessageSource;
030import org.springframework.context.support.MessageSourceResourceBundle;
031import org.springframework.context.support.ResourceBundleMessageSource;
032import org.springframework.lang.Nullable;
033
034/**
035 * Helper class for preparing JSTL views,
036 * in particular for exposing a JSTL localization context.
037 *
038 * @author Juergen Hoeller
039 * @since 20.08.2003
040 */
041public abstract class JstlUtils {
042
043        /**
044         * Checks JSTL's "javax.servlet.jsp.jstl.fmt.localizationContext"
045         * context-param and creates a corresponding child message source,
046         * with the provided Spring-defined MessageSource as parent.
047         * @param servletContext the ServletContext we're running in
048         * (to check JSTL-related context-params in {@code web.xml})
049         * @param messageSource the MessageSource to expose, typically
050         * the ApplicationContext of the current DispatcherServlet
051         * @return the MessageSource to expose to JSTL; first checking the
052         * JSTL-defined bundle, then the Spring-defined MessageSource
053         * @see org.springframework.context.ApplicationContext
054         */
055        public static MessageSource getJstlAwareMessageSource(
056                        @Nullable ServletContext servletContext, MessageSource messageSource) {
057
058                if (servletContext != null) {
059                        String jstlInitParam = servletContext.getInitParameter(Config.FMT_LOCALIZATION_CONTEXT);
060                        if (jstlInitParam != null) {
061                                // Create a ResourceBundleMessageSource for the specified resource bundle
062                                // basename in the JSTL context-param in web.xml, wiring it with the given
063                                // Spring-defined MessageSource as parent.
064                                ResourceBundleMessageSource jstlBundleWrapper = new ResourceBundleMessageSource();
065                                jstlBundleWrapper.setBasename(jstlInitParam);
066                                jstlBundleWrapper.setParentMessageSource(messageSource);
067                                return jstlBundleWrapper;
068                        }
069                }
070                return messageSource;
071        }
072
073        /**
074         * Exposes JSTL-specific request attributes specifying locale
075         * and resource bundle for JSTL's formatting and message tags,
076         * using Spring's locale and MessageSource.
077         * @param request the current HTTP request
078         * @param messageSource the MessageSource to expose,
079         * typically the current ApplicationContext (may be {@code null})
080         * @see #exposeLocalizationContext(RequestContext)
081         */
082        public static void exposeLocalizationContext(HttpServletRequest request, @Nullable MessageSource messageSource) {
083                Locale jstlLocale = RequestContextUtils.getLocale(request);
084                Config.set(request, Config.FMT_LOCALE, jstlLocale);
085                TimeZone timeZone = RequestContextUtils.getTimeZone(request);
086                if (timeZone != null) {
087                        Config.set(request, Config.FMT_TIME_ZONE, timeZone);
088                }
089                if (messageSource != null) {
090                        LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, request);
091                        Config.set(request, Config.FMT_LOCALIZATION_CONTEXT, jstlContext);
092                }
093        }
094
095        /**
096         * Exposes JSTL-specific request attributes specifying locale
097         * and resource bundle for JSTL's formatting and message tags,
098         * using Spring's locale and MessageSource.
099         * @param requestContext the context for the current HTTP request,
100         * including the ApplicationContext to expose as MessageSource
101         */
102        public static void exposeLocalizationContext(RequestContext requestContext) {
103                Config.set(requestContext.getRequest(), Config.FMT_LOCALE, requestContext.getLocale());
104                TimeZone timeZone = requestContext.getTimeZone();
105                if (timeZone != null) {
106                        Config.set(requestContext.getRequest(), Config.FMT_TIME_ZONE, timeZone);
107                }
108                MessageSource messageSource = getJstlAwareMessageSource(
109                                requestContext.getServletContext(), requestContext.getMessageSource());
110                LocalizationContext jstlContext = new SpringLocalizationContext(messageSource, requestContext.getRequest());
111                Config.set(requestContext.getRequest(), Config.FMT_LOCALIZATION_CONTEXT, jstlContext);
112        }
113
114
115        /**
116         * Spring-specific LocalizationContext adapter that merges session-scoped
117         * JSTL LocalizationContext/Locale attributes with the local Spring request context.
118         */
119        private static class SpringLocalizationContext extends LocalizationContext {
120
121                private final MessageSource messageSource;
122
123                private final HttpServletRequest request;
124
125                public SpringLocalizationContext(MessageSource messageSource, HttpServletRequest request) {
126                        this.messageSource = messageSource;
127                        this.request = request;
128                }
129
130                @Override
131                public ResourceBundle getResourceBundle() {
132                        HttpSession session = this.request.getSession(false);
133                        if (session != null) {
134                                Object lcObject = Config.get(session, Config.FMT_LOCALIZATION_CONTEXT);
135                                if (lcObject instanceof LocalizationContext) {
136                                        ResourceBundle lcBundle = ((LocalizationContext) lcObject).getResourceBundle();
137                                        return new MessageSourceResourceBundle(this.messageSource, getLocale(), lcBundle);
138                                }
139                        }
140                        return new MessageSourceResourceBundle(this.messageSource, getLocale());
141                }
142
143                @Override
144                public Locale getLocale() {
145                        HttpSession session = this.request.getSession(false);
146                        if (session != null) {
147                                Object localeObject = Config.get(session, Config.FMT_LOCALE);
148                                if (localeObject instanceof Locale) {
149                                        return (Locale) localeObject;
150                                }
151                        }
152                        return RequestContextUtils.getLocale(this.request);
153                }
154        }
155
156}