Open Source Repository

Home /spring/spring-web-servlet-3.0.5 | Repository Home


org/springframework/web/servlet/view/InternalResourceView.java
/*
 * Copyright 2002-2010 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.web.servlet.view;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.util.StringUtils;
import org.springframework.web.context.support.ContextExposingHttpServletRequest;
import org.springframework.web.util.WebUtils;

/**
 * Wrapper for a JSP or other resource within the same web application.
 * Exposes model objects as request attributes and forwards the request to
 * the specified resource URL using a {@link javax.servlet.RequestDispatcher}.
 *
 <p>A URL for this view is supposed to specify a resource within the web
 * application, suitable for RequestDispatcher's <code>forward</code> or
 <code>include</code> method.
 *
 <p>If operating within an already included request or within a response that
 * has already been committed, this view will fall back to an include instead of
 * a forward. This can be enforced by calling <code>response.flushBuffer()</code>
 * (which will commit the response) before rendering the view.
 *
 <p>Typical usage with {@link InternalResourceViewResolver} looks as follows,
 * from the perspective of the DispatcherServlet context definition:
 *
 <pre class="code">&lt;bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver"&gt;
 *   &lt;property name="prefix" value="/WEB-INF/jsp/"/&gt;
 *   &lt;property name="suffix" value=".jsp"/&gt;
 * &lt;/bean&gt;</pre>
 *
 * Every view name returned from a handler will be translated to a JSP
 * resource (for example: "myView" -> "/WEB-INF/jsp/myView.jsp"), using
 * this view class by default.
 *
 @author Rod Johnson
 @author Juergen Hoeller
 @author Rob Harrop
 @see javax.servlet.RequestDispatcher#forward
 @see javax.servlet.RequestDispatcher#include
 @see javax.servlet.ServletResponse#flushBuffer
 @see InternalResourceViewResolver
 @see JstlView
 */
public class InternalResourceView extends AbstractUrlBasedView {

  private boolean alwaysInclude = false;

  private volatile Boolean exposeForwardAttributes;

  private boolean exposeContextBeansAsAttributes = false;

  private Set<String> exposedContextBeanNames;

  private boolean preventDispatchLoop = false;


  /**
   * Constructor for use as a bean.
   @see #setUrl
   @see #setAlwaysInclude
   */
  public InternalResourceView() {
  }

  /**
   * Create a new InternalResourceView with the given URL.
   @param url the URL to forward to
   @see #setAlwaysInclude
   */
  public InternalResourceView(String url) {
    super(url);
  }

  /**
   * Create a new InternalResourceView with the given URL.
   @param url the URL to forward to
   @param alwaysInclude whether to always include the view rather than forward to it
   */
  public InternalResourceView(String url, boolean alwaysInclude) {
    super(url);
    this.alwaysInclude = alwaysInclude;
  }


  /**
   * Specify whether to always include the view rather than forward to it.
   <p>Default is "false". Switch this flag on to enforce the use of a
   * Servlet include, even if a forward would be possible.
   @see javax.servlet.RequestDispatcher#forward
   @see javax.servlet.RequestDispatcher#include
   @see #useInclude(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
   */
  public void setAlwaysInclude(boolean alwaysInclude) {
    this.alwaysInclude = alwaysInclude;
  }

  /**
   * Set whether to explictly expose the Servlet 2.4 forward request attributes
   * when forwarding to the underlying view resource.
   <p>Default is "true" on Servlet containers up until 2.4, and "false" for
   * Servlet 2.5 and above. Note that Servlet containers at 2.4 level and above
   * should expose those attributes automatically! This InternalResourceView
   * feature exists for Servlet 2.3 containers and misbehaving 2.4 containers.
   */
  public void setExposeForwardAttributes(boolean exposeForwardAttributes) {
    this.exposeForwardAttributes = exposeForwardAttributes;
  }

  /**
   * Set whether to make all Spring beans in the application context accessible
   * as request attributes, through lazy checking once an attribute gets accessed.
   <p>This will make all such beans accessible in plain <code>${...}</code>
   * expressions in a JSP 2.0 page, as well as in JSTL's <code>c:out</code>
   * value expressions.
   <p>Default is "false". Switch this flag on to transparently expose all
   * Spring beans in the request attribute namespace.
   <p><b>NOTE:</b> Context beans will override any custom request or session
   * attributes of the same name that have been manually added. However, model
   * attributes (as explicitly exposed to this view) of the same name will
   * always override context beans.
   @see #getRequestToExpose
   */
  public void setExposeContextBeansAsAttributes(boolean exposeContextBeansAsAttributes) {
    this.exposeContextBeansAsAttributes = exposeContextBeansAsAttributes;
  }

  /**
   * Specify the names of beans in the context which are supposed to be exposed.
   * If this is non-null, only the specified beans are eligible for exposure as
   * attributes.
   <p>If you'd like to expose all Spring beans in the application context, switch
   * the {@link #setExposeContextBeansAsAttributes "exposeContextBeansAsAttributes"}
   * flag on but do not list specific bean names for this property.
   */
  public void setExposedContextBeanNames(String[] exposedContextBeanNames) {
    this.exposedContextBeanNames = new HashSet<String>(Arrays.asList(exposedContextBeanNames));
  }

  /**
   * Set whether to explicitly prevent dispatching back to the
   * current handler path.
   <p>Default is "false". Switch this to "true" for convention-based
   * views where a dispatch back to the current handler path is a
   * definitive error.
   */
  public void setPreventDispatchLoop(boolean preventDispatchLoop) {
    this.preventDispatchLoop = preventDispatchLoop;
  }

  /**
   * An ApplicationContext is not strictly required for InternalResourceView.
   */
  @Override
  protected boolean isContextRequired() {
    return false;
  }

  /**
   * Checks whether we need to explicitly expose the Servlet 2.4 request attributes
   * by default.
   @see #setExposeForwardAttributes
   @see #exposeForwardRequestAttributes(javax.servlet.http.HttpServletRequest)
   */
  @Override
  protected void initServletContext(ServletContext sc) {
    if (this.exposeForwardAttributes == null && sc.getMajorVersion() == && sc.getMinorVersion() 5) {
      this.exposeForwardAttributes = Boolean.TRUE;
    }
  }


  /**
   * Render the internal resource given the specified model.
   * This includes setting the model as request attributes.
   */
  @Override
  protected void renderMergedOutputModel(
      Map<String, Object> model, HttpServletRequest request, HttpServletResponse responsethrows Exception {

    // Determine which request handle to expose to the RequestDispatcher.
    HttpServletRequest requestToExpose = getRequestToExpose(request);

    // Expose the model object as request attributes.
    exposeModelAsRequestAttributes(model, requestToExpose);

    // Expose helpers as request attributes, if any.
    exposeHelpers(requestToExpose);

    // Determine the path for the request dispatcher.
    String dispatcherPath = prepareForRendering(requestToExpose, response);

    // Obtain a RequestDispatcher for the target resource (typically a JSP).
    RequestDispatcher rd = getRequestDispatcher(requestToExpose, dispatcherPath);
    if (rd == null) {
      throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
          "]: Check that the corresponding file exists within your web application archive!");
    }

    // If already included or response already committed, perform include, else forward.
    if (useInclude(requestToExpose, response)) {
      response.setContentType(getContentType());
      if (logger.isDebugEnabled()) {
        logger.debug("Including resource [" + getUrl() "] in InternalResourceView '" + getBeanName() "'");
      }
      rd.include(requestToExpose, response);
    }

    else {
      // Note: The forwarded resource is supposed to determine the content type itself.
      exposeForwardRequestAttributes(requestToExpose);
      if (logger.isDebugEnabled()) {
        logger.debug("Forwarding to resource [" + getUrl() "] in InternalResourceView '" + getBeanName() "'");
      }
      rd.forward(requestToExpose, response);
    }
  }

  /**
   * Get the request handle to expose to the RequestDispatcher, i.e. to the view.
   <p>The default implementation wraps the original request for exposure of
   * Spring beans as request attributes (if demanded).
   @param originalRequest the original servlet request as provided by the engine
   @return the wrapped request, or the original request if no wrapping is necessary
   @see #setExposeContextBeansAsAttributes
   @see org.springframework.web.context.support.ContextExposingHttpServletRequest
   */
  protected HttpServletRequest getRequestToExpose(HttpServletRequest originalRequest) {
    if (this.exposeContextBeansAsAttributes || this.exposedContextBeanNames != null) {
      return new ContextExposingHttpServletRequest(
          originalRequest, getWebApplicationContext()this.exposedContextBeanNames);
    }
    return originalRequest;
  }

  /**
   * Expose helpers unique to each rendering operation. This is necessary so that
   * different rendering operations can't overwrite each other's contexts etc.
   <p>Called by {@link #renderMergedOutputModel(Map, HttpServletRequest, HttpServletResponse)}.
   * The default implementation is empty. This method can be overridden to add
   * custom helpers as request attributes.
   @param request current HTTP request
   @throws Exception if there's a fatal error while we're adding attributes
   @see #renderMergedOutputModel
   @see JstlView#exposeHelpers
   */
  protected void exposeHelpers(HttpServletRequest requestthrows Exception {
  }

  /**
   * Prepare for rendering, and determine the request dispatcher path
   * to forward to (or to include).
   <p>This implementation simply returns the configured URL.
   * Subclasses can override this to determine a resource to render,
   * typically interpreting the URL in a different manner.
   @param request current HTTP request
   @param response current HTTP response
   @return the request dispatcher path to use
   @throws Exception if preparations failed
   @see #getUrl()
   */
  protected String prepareForRendering(HttpServletRequest request, HttpServletResponse response)
      throws Exception {

    String path = getUrl();
    if (this.preventDispatchLoop) {
      String uri = request.getRequestURI();
      if (path.startsWith("/"? uri.equals(path: uri.equals(StringUtils.applyRelativePath(uri, path))) {
        throw new ServletException("Circular view path [" + path + "]: would dispatch back " +
            "to the current handler URL [" + uri + "] again. Check your ViewResolver setup! " +
            "(Hint: This may be the result of an unspecified view, due to default view name generation.)");
      }
    }
    return path;
  }

  /**
   * Obtain the RequestDispatcher to use for the forward/include.
   <p>The default implementation simply calls
   {@link HttpServletRequest#getRequestDispatcher(String)}.
   * Can be overridden in subclasses.
   @param request current HTTP request
   @param path the target URL (as returned from {@link #prepareForRendering})
   @return a corresponding RequestDispatcher
   */
  protected RequestDispatcher getRequestDispatcher(HttpServletRequest request, String path) {
    return request.getRequestDispatcher(path);
  }

  /**
   * Determine whether to use RequestDispatcher's <code>include</code> or
   <code>forward</code> method.
   <p>Performs a check whether an include URI attribute is found in the request,
   * indicating an include request, and whether the response has already been committed.
   * In both cases, an include will be performed, as a forward is not possible anymore.
   @param request current HTTP request
   @param response current HTTP response
   @return <code>true</code> for include, <code>false</code> for forward
   @see javax.servlet.RequestDispatcher#forward
   @see javax.servlet.RequestDispatcher#include
   @see javax.servlet.ServletResponse#isCommitted
   @see org.springframework.web.util.WebUtils#isIncludeRequest
   */
  protected boolean useInclude(HttpServletRequest request, HttpServletResponse response) {
    return (this.alwaysInclude || WebUtils.isIncludeRequest(request|| response.isCommitted());
  }

  /**
   * Expose the current request URI and paths as {@link HttpServletRequest}
   * attributes under the keys defined in the Servlet 2.4 specification,
   * for Servlet 2.3 containers as well as misbehaving Servlet 2.4 containers
   * (such as OC4J).
   <p>Does not expose the attributes on Servlet 2.5 or above, mainly for
   * GlassFish compatibility (GlassFish gets confused by pre-exposed attributes).
   * In any case, Servlet 2.5 containers should finally properly support
   * Servlet 2.4 features, shouldn't they...
   @param request current HTTP request
   @see org.springframework.web.util.WebUtils#exposeForwardRequestAttributes
   */
  protected void exposeForwardRequestAttributes(HttpServletRequest request) {
    if (this.exposeForwardAttributes != null && this.exposeForwardAttributes) {
      try {
        WebUtils.exposeForwardRequestAttributes(request);
      }
      catch (Exception ex) {
        // Servlet container rejected to set internal attributes, e.g. on TriFork.
        this.exposeForwardAttributes = Boolean.FALSE;
      }
    }
  }

}