001/*
002 * Copyright 2002-2016 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.view;
018
019import java.util.Enumeration;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import javax.servlet.ServletException;
024import javax.servlet.http.HttpServletRequest;
025import javax.servlet.http.HttpServletResponse;
026import javax.servlet.http.HttpSession;
027
028import org.springframework.web.servlet.support.RequestContext;
029
030/**
031 * Adapter base class for template-based view technologies such as FreeMarker,
032 * with the ability to use request and session attributes in their model and
033 * the option to expose helper objects for Spring's FreeMarker macro library.
034 *
035 * <p>JSP/JSTL and other view technologies automatically have access to the
036 * HttpServletRequest object and thereby the request/session attributes
037 * for the current user. Furthermore, they are able to create and cache
038 * helper objects as request attributes themselves.
039 *
040 * @author Juergen Hoeller
041 * @author Darren Davison
042 * @since 1.0.2
043 * @see AbstractTemplateViewResolver
044 * @see org.springframework.web.servlet.view.freemarker.FreeMarkerView
045 */
046public abstract class AbstractTemplateView extends AbstractUrlBasedView {
047
048        /**
049         * Variable name of the RequestContext instance in the template model,
050         * available to Spring's macros: e.g. for creating BindStatus objects.
051         */
052        public static final String SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE = "springMacroRequestContext";
053
054
055        private boolean exposeRequestAttributes = false;
056
057        private boolean allowRequestOverride = false;
058
059        private boolean exposeSessionAttributes = false;
060
061        private boolean allowSessionOverride = false;
062
063        private boolean exposeSpringMacroHelpers = true;
064
065
066        /**
067         * Set whether all request attributes should be added to the
068         * model prior to merging with the template. Default is "false".
069         */
070        public void setExposeRequestAttributes(boolean exposeRequestAttributes) {
071                this.exposeRequestAttributes = exposeRequestAttributes;
072        }
073
074        /**
075         * Set whether HttpServletRequest attributes are allowed to override (hide)
076         * controller generated model attributes of the same name. Default is "false",
077         * which causes an exception to be thrown if request attributes of the same
078         * name as model attributes are found.
079         */
080        public void setAllowRequestOverride(boolean allowRequestOverride) {
081                this.allowRequestOverride = allowRequestOverride;
082        }
083
084        /**
085         * Set whether all HttpSession attributes should be added to the
086         * model prior to merging with the template. Default is "false".
087         */
088        public void setExposeSessionAttributes(boolean exposeSessionAttributes) {
089                this.exposeSessionAttributes = exposeSessionAttributes;
090        }
091
092        /**
093         * Set whether HttpSession attributes are allowed to override (hide)
094         * controller generated model attributes of the same name. Default is "false",
095         * which causes an exception to be thrown if session attributes of the same
096         * name as model attributes are found.
097         */
098        public void setAllowSessionOverride(boolean allowSessionOverride) {
099                this.allowSessionOverride = allowSessionOverride;
100        }
101
102        /**
103         * Set whether to expose a RequestContext for use by Spring's macro library,
104         * under the name "springMacroRequestContext". Default is "true".
105         * <p>Currently needed for Spring's FreeMarker default macros.
106         * Note that this is <i>not</i> required for templates that use HTML forms
107         * <i>unless</i> you wish to take advantage of the Spring helper macros.
108         * @see #SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE
109         */
110        public void setExposeSpringMacroHelpers(boolean exposeSpringMacroHelpers) {
111                this.exposeSpringMacroHelpers = exposeSpringMacroHelpers;
112        }
113
114
115        @Override
116        protected final void renderMergedOutputModel(
117                        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
118
119                if (this.exposeRequestAttributes) {
120                        Map<String, Object> exposed = null;
121                        for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {
122                                String attribute = en.nextElement();
123                                if (model.containsKey(attribute) && !this.allowRequestOverride) {
124                                        throw new ServletException("Cannot expose request attribute '" + attribute +
125                                                "' because of an existing model object of the same name");
126                                }
127                                Object attributeValue = request.getAttribute(attribute);
128                                if (logger.isDebugEnabled()) {
129                                        exposed = exposed != null ? exposed : new LinkedHashMap<>();
130                                        exposed.put(attribute, attributeValue);
131                                }
132                                model.put(attribute, attributeValue);
133                        }
134                        if (logger.isTraceEnabled() && exposed != null) {
135                                logger.trace("Exposed request attributes to model: " + exposed);
136                        }
137                }
138
139                if (this.exposeSessionAttributes) {
140                        HttpSession session = request.getSession(false);
141                        if (session != null) {
142                                Map<String, Object> exposed = null;
143                                for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) {
144                                        String attribute = en.nextElement();
145                                        if (model.containsKey(attribute) && !this.allowSessionOverride) {
146                                                throw new ServletException("Cannot expose session attribute '" + attribute +
147                                                        "' because of an existing model object of the same name");
148                                        }
149                                        Object attributeValue = session.getAttribute(attribute);
150                                        if (logger.isDebugEnabled()) {
151                                                exposed = exposed != null ? exposed : new LinkedHashMap<>();
152                                                exposed.put(attribute, attributeValue);
153                                        }
154                                        model.put(attribute, attributeValue);
155                                }
156                                if (logger.isTraceEnabled() && exposed != null) {
157                                        logger.trace("Exposed session attributes to model: " + exposed);
158                                }
159                        }
160                }
161
162                if (this.exposeSpringMacroHelpers) {
163                        if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
164                                throw new ServletException(
165                                                "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
166                                                "' because of an existing model object of the same name");
167                        }
168                        // Expose RequestContext instance for Spring macros.
169                        model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
170                                        new RequestContext(request, response, getServletContext(), model));
171                }
172
173                applyContentType(response);
174
175                if (logger.isDebugEnabled()) {
176                        logger.debug("Rendering [" + getUrl() + "]");
177                }
178
179                renderMergedTemplateModel(model, request, response);
180        }
181
182        /**
183         * Apply this view's content type as specified in the "contentType"
184         * bean property to the given response.
185         * <p>Only applies the view's contentType if no content type has been
186         * set on the response before. This allows handlers to override the
187         * default content type beforehand.
188         * @param response current HTTP response
189         * @see #setContentType
190         */
191        protected void applyContentType(HttpServletResponse response)   {
192                if (response.getContentType() == null) {
193                        response.setContentType(getContentType());
194                }
195        }
196
197        /**
198         * Subclasses must implement this method to actually render the view.
199         * @param model combined output Map, with request attributes and
200         * session attributes merged into it if required
201         * @param request current HTTP request
202         * @param response current HTTP response
203         * @throws Exception if rendering failed
204         */
205        protected abstract void renderMergedTemplateModel(
206                        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
207
208}