001/*
002 * Copyright 2002-2015 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.Map;
021import javax.servlet.ServletException;
022import javax.servlet.http.HttpServletRequest;
023import javax.servlet.http.HttpServletResponse;
024import javax.servlet.http.HttpSession;
025
026import org.springframework.web.servlet.support.RequestContext;
027
028/**
029 * Adapter base class for template-based view technologies such as
030 * Velocity and FreeMarker, with the ability to use request and session
031 * attributes in their model and the option to expose helper objects
032 * for Spring's Velocity/FreeMarker macro library.
033 *
034 * <p>JSP/JSTL and other view technologies automatically have access to the
035 * HttpServletRequest object and thereby the request/session attributes
036 * for the current user. Furthermore, they are able to create and cache
037 * helper objects as request attributes themselves.
038 *
039 * @author Juergen Hoeller
040 * @author Darren Davison
041 * @since 1.0.2
042 * @see AbstractTemplateViewResolver
043 * @see org.springframework.web.servlet.view.velocity.VelocityView
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 Velocity and FreeMarker default macros.
106         * Note that this is <i>not</i> required for templates that use HTML
107         * forms <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                        for (Enumeration<String> en = request.getAttributeNames(); en.hasMoreElements();) {
121                                String attribute = en.nextElement();
122                                if (model.containsKey(attribute) && !this.allowRequestOverride) {
123                                        throw new ServletException("Cannot expose request attribute '" + attribute +
124                                                "' because of an existing model object of the same name");
125                                }
126                                Object attributeValue = request.getAttribute(attribute);
127                                if (logger.isDebugEnabled()) {
128                                        logger.debug("Exposing request attribute '" + attribute +
129                                                        "' with value [" + attributeValue + "] to model");
130                                }
131                                model.put(attribute, attributeValue);
132                        }
133                }
134
135                if (this.exposeSessionAttributes) {
136                        HttpSession session = request.getSession(false);
137                        if (session != null) {
138                                for (Enumeration<String> en = session.getAttributeNames(); en.hasMoreElements();) {
139                                        String attribute = en.nextElement();
140                                        if (model.containsKey(attribute) && !this.allowSessionOverride) {
141                                                throw new ServletException("Cannot expose session attribute '" + attribute +
142                                                        "' because of an existing model object of the same name");
143                                        }
144                                        Object attributeValue = session.getAttribute(attribute);
145                                        if (logger.isDebugEnabled()) {
146                                                logger.debug("Exposing session attribute '" + attribute +
147                                                                "' with value [" + attributeValue + "] to model");
148                                        }
149                                        model.put(attribute, attributeValue);
150                                }
151                        }
152                }
153
154                if (this.exposeSpringMacroHelpers) {
155                        if (model.containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
156                                throw new ServletException(
157                                                "Cannot expose bind macro helper '" + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE +
158                                                "' because of an existing model object of the same name");
159                        }
160                        // Expose RequestContext instance for Spring macros.
161                        model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
162                                        new RequestContext(request, response, getServletContext(), model));
163                }
164
165                applyContentType(response);
166
167                renderMergedTemplateModel(model, request, response);
168        }
169
170        /**
171         * Apply this view's content type as specified in the "contentType"
172         * bean property to the given response.
173         * <p>Only applies the view's contentType if no content type has been
174         * set on the response before. This allows handlers to override the
175         * default content type beforehand.
176         * @param response current HTTP response
177         * @see #setContentType
178         */
179        protected void applyContentType(HttpServletResponse response)   {
180                if (response.getContentType() == null) {
181                        response.setContentType(getContentType());
182                }
183        }
184
185        /**
186         * Subclasses must implement this method to actually render the view.
187         * @param model combined output Map, with request attributes and
188         * session attributes merged into it if required
189         * @param request current HTTP request
190         * @param response current HTTP response
191         * @throws Exception if rendering failed
192         */
193        protected abstract void renderMergedTemplateModel(
194                        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
195
196}