Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » web » servlet » view » [javadoc | source]
    1   /*
    2    * Copyright 2002-2008 the original author or authors.
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *      http://www.apache.org/licenses/LICENSE-2.0
    9    *
   10    * Unless required by applicable law or agreed to in writing, software
   11    * distributed under the License is distributed on an "AS IS" BASIS,
   12    * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   13    * See the License for the specific language governing permissions and
   14    * limitations under the License.
   15    */
   16   
   17   package org.springframework.web.servlet.view;
   18   
   19   import java.io.ByteArrayOutputStream;
   20   import java.io.IOException;
   21   import java.util.Collections;
   22   import java.util.HashMap;
   23   import java.util.Iterator;
   24   import java.util.Map;
   25   import java.util.Properties;
   26   import java.util.StringTokenizer;
   27   import javax.servlet.ServletOutputStream;
   28   import javax.servlet.http.HttpServletRequest;
   29   import javax.servlet.http.HttpServletResponse;
   30   
   31   import org.springframework.beans.factory.BeanNameAware;
   32   import org.springframework.util.CollectionUtils;
   33   import org.springframework.web.context.support.WebApplicationObjectSupport;
   34   import org.springframework.web.servlet.View;
   35   import org.springframework.web.servlet.support.RequestContext;
   36   
   37   /**
   38    * Abstract base class for {@link org.springframework.web.servlet.View}
   39    * implementations. Subclasses should be JavaBeans, to allow for
   40    * convenient configuration as Spring-managed bean instances.
   41    *
   42    * <p>Provides support for static attributes, to be made available to the view,
   43    * with a variety of ways to specify them. Static attributes will be merged
   44    * with the given dynamic attributes (the model that the controller returned)
   45    * for each render operation.
   46    *
   47    * <p>Extends {@link WebApplicationObjectSupport}, which will be helpful to
   48    * some views. Subclasses just need to implement the actual rendering.
   49    *
   50    * @author Rod Johnson
   51    * @author Juergen Hoeller
   52    * @see #setAttributes
   53    * @see #setAttributesMap
   54    * @see #renderMergedOutputModel
   55    */
   56   public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
   57   
   58   	/** Default content type. Overridable as bean property. */
   59   	public static final String DEFAULT_CONTENT_TYPE = "text/html;charset=ISO-8859-1";
   60   
   61   	/** Initial size for the temporary output byte array (if any) */
   62   	private static final int OUTPUT_BYTE_ARRAY_INITIAL_SIZE = 4096;
   63   
   64   
   65   	private String beanName;
   66   
   67   	private String contentType = DEFAULT_CONTENT_TYPE;
   68   
   69   	private String requestContextAttribute;
   70   
   71   	/** Map of static attributes, keyed by attribute name (String) */
   72   	private final Map	staticAttributes = new HashMap();
   73   
   74   
   75   	/**
   76   	 * Set the view's name. Helpful for traceability.
   77   	 * <p>Framework code must call this when constructing views.
   78   	 */
   79   	public void setBeanName(String beanName) {
   80   		this.beanName = beanName;
   81   	}
   82   
   83   	/**
   84   	 * Return the view's name. Should never be <code>null</code>,
   85   	 * if the view was correctly configured.
   86   	 */
   87   	public String getBeanName() {
   88   		return this.beanName;
   89   	}
   90   
   91   	/**
   92   	 * Set the content type for this view.
   93   	 * Default is "text/html;charset=ISO-8859-1".
   94   	 * <p>May be ignored by subclasses if the view itself is assumed
   95   	 * to set the content type, e.g. in case of JSPs.
   96   	 */
   97   	public void setContentType(String contentType) {
   98   		this.contentType = contentType;
   99   	}
  100   
  101   	/**
  102   	 * Return the content type for this view.
  103   	 */
  104   	public String getContentType() {
  105   		return this.contentType;
  106   	}
  107   
  108   	/**
  109   	 * Set the name of the RequestContext attribute for this view.
  110   	 * Default is none.
  111   	 */
  112   	public void setRequestContextAttribute(String requestContextAttribute) {
  113   		this.requestContextAttribute = requestContextAttribute;
  114   	}
  115   
  116   	/**
  117   	 * Return the name of the RequestContext attribute, if any.
  118   	 */
  119   	public String getRequestContextAttribute() {
  120   		return this.requestContextAttribute;
  121   	}
  122   
  123   	/**
  124   	 * Set static attributes as a CSV string.
  125   	 * Format is: attname0={value1},attname1={value1}
  126   	 * <p>"Static" attributes are fixed attributes that are specified in
  127   	 * the View instance configuration. "Dynamic" attributes, on the other hand,
  128   	 * are values passed in as part of the model.
  129   	 */
  130   	public void setAttributesCSV(String propString) throws IllegalArgumentException {
  131   		if (propString != null) {
  132   			StringTokenizer st = new StringTokenizer(propString, ",");
  133   			while (st.hasMoreTokens()) {
  134   				String tok = st.nextToken();
  135   				int eqIdx = tok.indexOf("=");
  136   				if (eqIdx == -1) {
  137   					throw new IllegalArgumentException("Expected = in attributes CSV string '" + propString + "'");
  138   				}
  139   				if (eqIdx >= tok.length() - 2) {
  140   					throw new IllegalArgumentException(
  141   							"At least 2 characters ([]) required in attributes CSV string '" + propString + "'");
  142   				}
  143   				String name = tok.substring(0, eqIdx);
  144   				String value = tok.substring(eqIdx + 1);
  145   
  146   				// Delete first and last characters of value: { and }
  147   				value = value.substring(1);
  148   				value = value.substring(0, value.length() - 1);
  149   
  150   				addStaticAttribute(name, value);
  151   			}
  152   		}
  153   	}
  154   
  155   	/**
  156   	 * Set static attributes for this view from a
  157   	 * <code>java.util.Properties</code> object.
  158   	 * <p>"Static" attributes are fixed attributes that are specified in
  159   	 * the View instance configuration. "Dynamic" attributes, on the other hand,
  160   	 * are values passed in as part of the model.
  161   	 * <p>This is the most convenient way to set static attributes. Note that
  162   	 * static attributes can be overridden by dynamic attributes, if a value
  163   	 * with the same name is included in the model.
  164   	 * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
  165   	 * or a "props" element in XML bean definitions.
  166   	 * @see org.springframework.beans.propertyeditors.PropertiesEditor
  167   	 */
  168   	public void setAttributes(Properties attributes) {
  169   		CollectionUtils.mergePropertiesIntoMap(attributes, this.staticAttributes);
  170   	}
  171   
  172   	/**
  173   	 * Set static attributes for this view from a Map. This allows to set
  174   	 * any kind of attribute values, for example bean references.
  175   	 * <p>"Static" attributes are fixed attributes that are specified in
  176   	 * the View instance configuration. "Dynamic" attributes, on the other hand,
  177   	 * are values passed in as part of the model.
  178   	 * <p>Can be populated with a "map" or "props" element in XML bean definitions.
  179   	 * @param attributes Map with name Strings as keys and attribute objects as values
  180   	 */
  181   	public void setAttributesMap(Map attributes) {
  182   		if (attributes != null) {
  183   			Iterator it = attributes.entrySet().iterator();
  184   			while (it.hasNext()) {
  185   				Map.Entry entry = (Map.Entry) it.next();
  186   				Object key = entry.getKey();
  187   				if (!(key instanceof String)) {
  188   					throw new IllegalArgumentException(
  189   							"Invalid attribute key [" + key + "]: only Strings allowed");
  190   				}
  191   				addStaticAttribute((String) key, entry.getValue());
  192   			}
  193   		}
  194   	}
  195   
  196   	/**
  197   	 * Allow Map access to the static attributes of this view,
  198   	 * with the option to add or override specific entries.
  199   	 * <p>Useful for specifying entries directly, for example via
  200   	 * "attributesMap[myKey]". This is particularly useful for
  201   	 * adding or overriding entries in child view definitions.
  202   	 */
  203   	public Map getAttributesMap() {
  204   		return this.staticAttributes;
  205   	}
  206   
  207   	/**
  208   	 * Add static data to this view, exposed in each view.
  209   	 * <p>"Static" attributes are fixed attributes that are specified in
  210   	 * the View instance configuration. "Dynamic" attributes, on the other hand,
  211   	 * are values passed in as part of the model.
  212   	 * <p>Must be invoked before any calls to <code>render</code>.
  213   	 * @param name the name of the attribute to expose
  214   	 * @param value the attribute value to expose
  215   	 * @see #render
  216   	 */
  217   	public void addStaticAttribute(String name, Object value) {
  218   		this.staticAttributes.put(name, value);
  219   	}
  220   
  221   	/**
  222   	 * Return the static attributes for this view. Handy for testing.
  223   	 * <p>Returns an unmodifiable Map, as this is not intended for
  224   	 * manipulating the Map but rather just for checking the contents.
  225   	 * @return the static attributes in this view
  226   	 */
  227   	public Map getStaticAttributes() {
  228   		return Collections.unmodifiableMap(this.staticAttributes);
  229   	}
  230   
  231   
  232   	/**
  233   	 * Prepares the view given the specified model, merging it with static
  234   	 * attributes and a RequestContext attribute, if necessary.
  235   	 * Delegates to renderMergedOutputModel for the actual rendering.
  236   	 * @see #renderMergedOutputModel
  237   	 */
  238   	public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  239   		if (logger.isTraceEnabled()) {
  240   			logger.trace("Rendering view with name '" + this.beanName + "' with model " + model +
  241   				" and static attributes " + this.staticAttributes);
  242   		}
  243   
  244   		// Consolidate static and dynamic model attributes.
  245   		Map mergedModel = new HashMap(this.staticAttributes.size() + (model != null ? model.size() : 0));
  246   		mergedModel.putAll(this.staticAttributes);
  247   		if (model != null) {
  248   			mergedModel.putAll(model);
  249   		}
  250   
  251   		// Expose RequestContext?
  252   		if (this.requestContextAttribute != null) {
  253   			mergedModel.put(this.requestContextAttribute, createRequestContext(request, mergedModel));
  254   		}
  255   
  256   		prepareResponse(request, response);
  257   		renderMergedOutputModel(mergedModel, request, response);
  258   	}
  259   
  260   	/**
  261   	 * Create a RequestContext to expose under the specified attribute name.
  262   	 * <p>Default implementation creates a standard RequestContext instance for the
  263   	 * given request and model. Can be overridden in subclasses for custom instances.
  264   	 * @param request current HTTP request
  265   	 * @param model combined output Map (never <code>null</code>),
  266   	 * with dynamic values taking precedence over static attributes
  267   	 * @return the RequestContext instance
  268   	 * @see #setRequestContextAttribute
  269   	 * @see org.springframework.web.servlet.support.RequestContext
  270   	 */
  271   	protected RequestContext createRequestContext(HttpServletRequest request, Map model) {
  272   		return new RequestContext(request, getServletContext(), model);
  273   	}
  274   
  275   	/**
  276   	 * Prepare the given response for rendering.
  277   	 * <p>The default implementation applies a workaround for an IE bug
  278   	 * when sending download content via HTTPS.
  279   	 * @param request current HTTP request
  280   	 * @param response current HTTP response
  281   	 */
  282   	protected void prepareResponse(HttpServletRequest request, HttpServletResponse response) {
  283   		if (generatesDownloadContent()) {
  284   			response.setHeader("Pragma", "private");
  285   			response.setHeader("Cache-Control", "private, must-revalidate");
  286   		}
  287   	}
  288   
  289   	/**
  290   	 * Return whether this view generates download content
  291   	 * (typically binary content like PDF or Excel files).
  292   	 * <p>The default implementation returns <code>false</code>. Subclasses are
  293   	 * encouraged to return <code>true</code> here if they know that they are
  294   	 * generating download content that requires temporary caching on the
  295   	 * client side, typically via the response OutputStream.
  296   	 * @see #prepareResponse
  297   	 * @see javax.servlet.http.HttpServletResponse#getOutputStream()
  298   	 */
  299   	protected boolean generatesDownloadContent() {
  300   		return false;
  301   	}
  302   
  303   	/**
  304   	 * Subclasses must implement this method to actually render the view.
  305   	 * <p>The first step will be preparing the request: In the JSP case,
  306   	 * this would mean setting model objects as request attributes.
  307   	 * The second step will be the actual rendering of the view,
  308   	 * for example including the JSP via a RequestDispatcher.
  309   	 * @param model combined output Map (never <code>null</code>),
  310   	 * with dynamic values taking precedence over static attributes
  311   	 * @param request current HTTP request
  312   	 * @param response current HTTP response
  313   	 * @throws Exception if rendering failed
  314   	 */
  315   	protected abstract void renderMergedOutputModel(
  316   			Map model, HttpServletRequest request, HttpServletResponse response) throws Exception;
  317   
  318   
  319   	/**
  320   	 * Expose the model objects in the given map as request attributes.
  321   	 * Names will be taken from the model Map.
  322   	 * This method is suitable for all resources reachable by {@link javax.servlet.RequestDispatcher}.
  323   	 * @param model Map of model objects to expose
  324   	 * @param request current HTTP request
  325   	 */
  326   	protected void exposeModelAsRequestAttributes(Map model, HttpServletRequest request) throws Exception {
  327   		Iterator it = model.entrySet().iterator();
  328   		while (it.hasNext()) {
  329   			Map.Entry entry = (Map.Entry) it.next();
  330   			if (!(entry.getKey() instanceof String)) {
  331   				throw new IllegalArgumentException(
  332   						"Invalid key [" + entry.getKey() + "] in model Map: only Strings allowed as model keys");
  333   			}
  334   			String modelName = (String) entry.getKey();
  335   			Object modelValue = entry.getValue();
  336   			if (modelValue != null) {
  337   				request.setAttribute(modelName, modelValue);
  338   				if (logger.isDebugEnabled()) {
  339   					logger.debug("Added model object '" + modelName + "' of type [" + modelValue.getClass().getName() +
  340   							"] to request in view with name '" + getBeanName() + "'");
  341   				}
  342   			}
  343   			else {
  344   				request.removeAttribute(modelName);
  345   				if (logger.isDebugEnabled()) {
  346   					logger.debug("Removed model object '" + modelName +
  347   							"' from request in view with name '" + getBeanName() + "'");
  348   				}
  349   			}
  350   		}
  351   	}
  352   
  353   	/**
  354   	 * Create a temporary OutputStream for this view.
  355   	 * <p>This is typically used as IE workaround, for setting the content length header
  356   	 * from the temporary stream before actually writing the content to the HTTP response.
  357   	 */
  358   	protected ByteArrayOutputStream createTemporaryOutputStream() {
  359   		return new ByteArrayOutputStream(OUTPUT_BYTE_ARRAY_INITIAL_SIZE);
  360   	}
  361   
  362   	/**
  363   	 * Write the given temporary OutputStream to the HTTP response.
  364   	 * @param response current HTTP response
  365   	 * @param baos the temporary OutputStream to write
  366   	 * @throws IOException if writing/flushing failed
  367   	 */
  368   	protected void writeToResponse(HttpServletResponse response, ByteArrayOutputStream baos) throws IOException {
  369   		// Write content type and also length (determined via byte array).
  370   		response.setContentType(getContentType());
  371   		response.setContentLength(baos.size());
  372   
  373   		// Flush byte array to servlet output stream.
  374   		ServletOutputStream out = response.getOutputStream();
  375   		baos.writeTo(out);
  376   		out.flush();
  377   	}
  378   
  379   
  380   	public String toString() {
  381   		StringBuffer sb = new StringBuffer(getClass().getName());
  382   		if (getBeanName() != null) {
  383   			sb.append(": name '").append(getBeanName()).append("'");
  384   		}
  385   		else {
  386   			sb.append(": unnamed");
  387   		}
  388   		return sb.toString();
  389   	}
  390   
  391   }

Save This Page
Home » spring-framework-2.5.6-with-dependencies » org.springframework » web » servlet » view » [javadoc | source]