1. /*
  2. * Copyright 2002-2004 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. package org.springframework.web.servlet.view;
  17. import java.util.Collections;
  18. import java.util.HashMap;
  19. import java.util.Iterator;
  20. import java.util.Map;
  21. import java.util.Properties;
  22. import java.util.StringTokenizer;
  23. import javax.servlet.http.HttpServletRequest;
  24. import javax.servlet.http.HttpServletResponse;
  25. import org.springframework.beans.factory.BeanNameAware;
  26. import org.springframework.web.context.support.WebApplicationObjectSupport;
  27. import org.springframework.web.servlet.View;
  28. import org.springframework.web.servlet.support.RequestContext;
  29. /**
  30. * Abstract View superclass. Standard framework View implementations
  31. * and application-specific custom Views can extend this class
  32. * to simplify their implementation. Subclasses should be JavaBeans.
  33. *
  34. * <p>Extends WebApplicationObjectSupport, which will be helpful to some views.
  35. * Handles static attributes, and merging static with dynamic attributes.
  36. * Subclasses just need to implement the actual rendering.
  37. *
  38. * @author Rod Johnson
  39. * @author Juergen Hoeller
  40. * @version $Id: AbstractView.java,v 1.10 2004/05/04 08:39:31 jhoeller Exp $
  41. * @see #renderMergedOutputModel
  42. */
  43. public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {
  44. /** Default content type. Overridable as bean property. */
  45. public static final String DEFAULT_CONTENT_TYPE = "text/html; charset=ISO-8859-1";
  46. private String beanName;
  47. private String contentType = DEFAULT_CONTENT_TYPE;
  48. private String requestContextAttribute;
  49. /** Map of static attributes, keyed by attribute name (String) */
  50. private final Map staticAttributes = new HashMap();
  51. /**
  52. * Set the view's name. Helpful for traceability.
  53. * Framework code must call this when constructing views.
  54. * @param beanName the view's name. May not be null.
  55. * Views should use this for log messages.
  56. */
  57. public void setBeanName(String beanName) {
  58. this.beanName = beanName;
  59. }
  60. /**
  61. * Return the view's name. Should never be null,
  62. * if the view was correctly configured.
  63. */
  64. public String getBeanName() {
  65. return beanName;
  66. }
  67. /**
  68. * Set the content type for this view.
  69. * Default is "text/html; charset=ISO-8859-1".
  70. * <p>May be ignored by subclasses if the view itself is assumed
  71. * to set the content type, e.g. in case of JSPs.
  72. * @param contentType content type for this view
  73. * @see #DEFAULT_CONTENT_TYPE
  74. */
  75. public void setContentType(String contentType) {
  76. this.contentType = contentType;
  77. }
  78. /**
  79. * Return the content type for this view.
  80. */
  81. public String getContentType() {
  82. return this.contentType;
  83. }
  84. /**
  85. * Set the name of the RequestContext attribute for this view.
  86. * Default is none.
  87. * @param requestContextAttribute name of the RequestContext attribute
  88. */
  89. public void setRequestContextAttribute(String requestContextAttribute) {
  90. this.requestContextAttribute = requestContextAttribute;
  91. }
  92. /**
  93. * Set static attributes from a java.util.Properties object. This is
  94. * the most convenient way to set static attributes. Note that static
  95. * attributes can be overridden by dynamic attributes, if a value
  96. * with the same name is included in the model.
  97. * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
  98. * or a "props" element in XML bean definitions.
  99. * @see org.springframework.beans.propertyeditors.PropertiesEditor
  100. */
  101. public void setAttributes(Properties props) {
  102. setAttributesMap(props);
  103. }
  104. /**
  105. * Set static attributes from a Map. This allows to set any kind
  106. * of attribute values, for example bean references.
  107. * <p>Can be populated with a "map" or "props" element in XML bean
  108. * definitions.
  109. * @param attributes Map with name Strings as keys and attribute
  110. * objects as values
  111. */
  112. public void setAttributesMap(Map attributes) {
  113. if (attributes != null) {
  114. Iterator itr = attributes.keySet().iterator();
  115. while (itr.hasNext()) {
  116. String name = (String) itr.next();
  117. Object value = attributes.get(name);
  118. addStaticAttribute(name, value);
  119. }
  120. }
  121. }
  122. /**
  123. * Set static attributes as a CSV string.
  124. * Format is: attname0={value1},attname1={value1}
  125. */
  126. public void setAttributesCSV(String propString) throws IllegalArgumentException {
  127. if (propString == null) {
  128. // leave static attributes unchanged
  129. return;
  130. }
  131. StringTokenizer st = new StringTokenizer(propString, ",");
  132. while (st.hasMoreTokens()) {
  133. String tok = st.nextToken();
  134. int eqIdx = tok.indexOf("=");
  135. if (eqIdx == -1) {
  136. throw new IllegalArgumentException("Expected = in View string '" + propString + "'");
  137. }
  138. if (eqIdx >= tok.length() - 2) {
  139. throw new IllegalArgumentException("At least 2 characters ([]) required in View string '" + propString + "'");
  140. }
  141. String name = tok.substring(0, eqIdx);
  142. String val = tok.substring(eqIdx + 1);
  143. // celete first and last characters of value: { and }
  144. val = val.substring(1);
  145. val = val.substring(0, val.length() - 1);
  146. if (logger.isDebugEnabled()) {
  147. logger.info("Set static attribute with name '" + name + "' and value [" + val + "] on view");
  148. }
  149. addStaticAttribute(name, val);
  150. }
  151. }
  152. /**
  153. * Add static data to this view, exposed in each view.
  154. * <p>Must be invoked before any calls to render().
  155. * @param name name of attribute to expose
  156. * @param value object to expose
  157. */
  158. public void addStaticAttribute(String name, Object value) {
  159. logger.debug("Set static attribute with name '" + name + "' and value [" + value + "] on view");
  160. this.staticAttributes.put(name, value);
  161. }
  162. /**
  163. * Handy for testing. Return the static attributes held in this view.
  164. * @return the static attributes in this view
  165. */
  166. public Map getStaticAttributes() {
  167. return Collections.unmodifiableMap(this.staticAttributes);
  168. }
  169. /**
  170. * Prepares the view given the specified model, merging it with static
  171. * attributes and a RequestContext attribute, if necessary.
  172. * Delegates to renderMergedOutputModel for the actual rendering.
  173. * @see #renderMergedOutputModel
  174. */
  175. public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
  176. if (logger.isDebugEnabled()) {
  177. logger.debug("Rendering view with name '" + this.beanName + "' with model " + model +
  178. " and static attributes " + this.staticAttributes);
  179. }
  180. // consolidate static and dynamic model attributes
  181. Map mergedModel = new HashMap(this.staticAttributes);
  182. if (model != null) {
  183. mergedModel.putAll(model);
  184. }
  185. // expose RequestContext?
  186. if (this.requestContextAttribute != null) {
  187. mergedModel.put(this.requestContextAttribute, new RequestContext(request, mergedModel));
  188. }
  189. renderMergedOutputModel(mergedModel, request, response);
  190. }
  191. /**
  192. * Subclasses must implement this method to actually render the view.
  193. * <p>The first step will be preparing the request: In the JSP case,
  194. * this would mean setting model objects as request attributes.
  195. * The second step will be the actual rendering of the view,
  196. * for example including the JSP via a RequestDispatcher.
  197. * @param model combined output Map, with dynamic values taking
  198. * precedence over static attributes
  199. * @param request current HTTP request
  200. * @param response current HTTP response
  201. * @throws Exception if rendering failed
  202. */
  203. protected abstract void renderMergedOutputModel(Map model, HttpServletRequest request,
  204. HttpServletResponse response) throws Exception;
  205. }