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"><bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"> 049 * <property name="prefix" value="/WEB-INF/jsp/"/> 050 * <property name="suffix" value=".jsp"/> 051 * </bean></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}