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