001/*
002 * Copyright 2002-2014 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.Map;
020import javax.servlet.RequestDispatcher;
021import javax.servlet.ServletException;
022import javax.servlet.http.HttpServletRequest;
023import javax.servlet.http.HttpServletResponse;
024
025import org.springframework.util.StringUtils;
026import org.springframework.web.util.WebUtils;
027
028/**
029 * Wrapper for a JSP or other resource within the same web application.
030 * Exposes model objects as request attributes and forwards the request to
031 * the specified resource URL using a {@link javax.servlet.RequestDispatcher}.
032 *
033 * <p>A URL for this view is supposed to specify a resource within the web
034 * application, suitable for RequestDispatcher's {@code forward} or
035 * {@code include} method.
036 *
037 * <p>If operating within an already included request or within a response that
038 * has already been committed, this view will fall back to an include instead of
039 * a forward. This can be enforced by calling {@code response.flushBuffer()}
040 * (which will commit the response) before rendering the view.
041 *
042 * <p>Typical usage with {@link InternalResourceViewResolver} looks as follows,
043 * from the perspective of the DispatcherServlet context definition:
044 *
045 * <pre class="code">&lt;bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"&gt;
046 *   &lt;property name="prefix" value="/WEB-INF/jsp/"/&gt;
047 *   &lt;property name="suffix" value=".jsp"/&gt;
048 * &lt;/bean&gt;</pre>
049 *
050 * Every view name returned from a handler will be translated to a JSP
051 * resource (for example: "myView" -> "/WEB-INF/jsp/myView.jsp"), using
052 * this view class by default.
053 *
054 * @author Rod Johnson
055 * @author Juergen Hoeller
056 * @author Rob Harrop
057 * @see javax.servlet.RequestDispatcher#forward
058 * @see javax.servlet.RequestDispatcher#include
059 * @see javax.servlet.ServletResponse#flushBuffer
060 * @see InternalResourceViewResolver
061 * @see JstlView
062 */
063public class InternalResourceView extends AbstractUrlBasedView {
064
065        private boolean alwaysInclude = false;
066
067        private boolean preventDispatchLoop = false;
068
069
070        /**
071         * Constructor for use as a bean.
072         * @see #setUrl
073         * @see #setAlwaysInclude
074         */
075        public InternalResourceView() {
076        }
077
078        /**
079         * Create a new InternalResourceView with the given URL.
080         * @param url the URL to forward to
081         * @see #setAlwaysInclude
082         */
083        public InternalResourceView(String url) {
084                super(url);
085        }
086
087        /**
088         * Create a new InternalResourceView with the given URL.
089         * @param url the URL to forward to
090         * @param alwaysInclude whether to always include the view rather than forward to it
091         */
092        public InternalResourceView(String url, boolean alwaysInclude) {
093                super(url);
094                this.alwaysInclude = alwaysInclude;
095        }
096
097
098        /**
099         * Specify whether to always include the view rather than forward to it.
100         * <p>Default is "false". Switch this flag on to enforce the use of a
101         * Servlet include, even if a forward would be possible.
102         * @see javax.servlet.RequestDispatcher#forward
103         * @see javax.servlet.RequestDispatcher#include
104         * @see #useInclude(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
105         */
106        public void setAlwaysInclude(boolean alwaysInclude) {
107                this.alwaysInclude = alwaysInclude;
108        }
109
110        /**
111         * Set whether to explicitly prevent dispatching back to the
112         * current handler path.
113         * <p>Default is "false". Switch this to "true" for convention-based
114         * views where a dispatch back to the current handler path is a
115         * definitive error.
116         */
117        public void setPreventDispatchLoop(boolean preventDispatchLoop) {
118                this.preventDispatchLoop = preventDispatchLoop;
119        }
120
121        /**
122         * An ApplicationContext is not strictly required for InternalResourceView.
123         */
124        @Override
125        protected boolean isContextRequired() {
126                return false;
127        }
128
129
130        /**
131         * Render the internal resource given the specified model.
132         * This includes setting the model as request attributes.
133         */
134        @Override
135        protected void renderMergedOutputModel(
136                        Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
137
138                // Expose the model object as request attributes.
139                exposeModelAsRequestAttributes(model, request);
140
141                // Expose helpers as request attributes, if any.
142                exposeHelpers(request);
143
144                // Determine the path for the request dispatcher.
145                String dispatcherPath = prepareForRendering(request, response);
146
147                // Obtain a RequestDispatcher for the target resource (typically a JSP).
148                RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
149                if (rd == null) {
150                        throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
151                                        "]: Check that the corresponding file exists within your web application archive!");
152                }
153
154                // If already included or response already committed, perform include, else forward.
155                if (useInclude(request, response)) {
156                        response.setContentType(getContentType());
157                        if (logger.isDebugEnabled()) {
158                                logger.debug("Including resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
159                        }
160                        rd.include(request, response);
161                }
162
163                else {
164                        // Note: The forwarded resource is supposed to determine the content type itself.
165                        if (logger.isDebugEnabled()) {
166                                logger.debug("Forwarding to resource [" + getUrl() + "] in InternalResourceView '" + getBeanName() + "'");
167                        }
168                        rd.forward(request, response);
169                }
170        }
171
172        /**
173         * Expose helpers unique to each rendering operation. This is necessary so that
174         * different rendering operations can't overwrite each other's contexts etc.
175         * <p>Called by {@link #renderMergedOutputModel(Map, HttpServletRequest, HttpServletResponse)}.
176         * The default implementation is empty. This method can be overridden to add
177         * custom helpers as request attributes.
178         * @param request current HTTP request
179         * @throws Exception if there's a fatal error while we're adding attributes
180         * @see #renderMergedOutputModel
181         * @see JstlView#exposeHelpers
182         */
183        protected void exposeHelpers(HttpServletRequest request) throws Exception {
184        }
185
186        /**
187         * Prepare for rendering, and determine the request dispatcher path
188         * to forward to (or to include).
189         * <p>This implementation simply returns the configured URL.
190         * Subclasses can override this to determine a resource to render,
191         * typically interpreting the URL in a different manner.
192         * @param request current HTTP request
193         * @param response current HTTP response
194         * @return the request dispatcher path to use
195         * @throws Exception if preparations failed
196         * @see #getUrl()
197         */
198        protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
199                        throws Exception {
200
201                String path = getUrl();
202                if (this.preventDispatchLoop) {
203                        String uri = request.getRequestURI();
204                        if (path.startsWith("/") ? uri.equals(path) : uri.equals(StringUtils.applyRelativePath(uri, path))) {
205                                throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
206                                                "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
207                                                "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
208                        }
209                }
210                return path;
211        }
212
213        /**
214         * Obtain the RequestDispatcher to use for the forward/include.
215         * <p>The default implementation simply calls
216         * {@link HttpServletRequest#getRequestDispatcher(String)}.
217         * Can be overridden in subclasses.
218         * @param request current HTTP request
219         * @param path the target URL (as returned from {@link #prepareForRendering})
220         * @return a corresponding RequestDispatcher
221         */
222        protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
223                return request.getRequestDispatcher(path);
224        }
225
226        /**
227         * Determine whether to use RequestDispatcher's {@code include} or
228         * {@code forward} method.
229         * <p>Performs a check whether an include URI attribute is found in the request,
230         * indicating an include request, and whether the response has already been committed.
231         * In both cases, an include will be performed, as a forward is not possible anymore.
232         * @param request current HTTP request
233         * @param response current HTTP response
234         * @return {@code true} for include, {@code false} for forward
235         * @see javax.servlet.RequestDispatcher#forward
236         * @see javax.servlet.RequestDispatcher#include
237         * @see javax.servlet.ServletResponse#isCommitted
238         * @see org.springframework.web.util.WebUtils#isIncludeRequest
239         */
240        protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
241                return (this.alwaysInclude || WebUtils.isIncludeRequest(request) || response.isCommitted());
242        }
243
244}