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