001: /*
002: * Copyright 2002-2007 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: * http://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:
017: package org.springframework.web.servlet.view;
018:
019: import java.util.Enumeration;
020: import java.util.Map;
021:
022: import javax.servlet.ServletException;
023: import javax.servlet.http.HttpServletRequest;
024: import javax.servlet.http.HttpServletResponse;
025: import javax.servlet.http.HttpSession;
026:
027: import org.springframework.util.ClassUtils;
028: import org.springframework.web.servlet.support.RequestContext;
029:
030: /**
031: * Adapter base class for template-based view technologies such as
032: * Velocity and FreeMarker, with the ability to use request and session
033: * attributes in their model and the option to expose helper objects
034: * for Spring's Velocity/FreeMarker macro library.
035: *
036: * <p>JSP/JSTL and other view technologies automatically have access to the
037: * HttpServletRequest object and thereby the request/session attributes
038: * for the current user. Furthermore, they are able to create and cache
039: * helper objects as request attributes themselves.
040: *
041: * @author Darren Davison
042: * @author Juergen Hoeller
043: * @since 1.0.2
044: * @see AbstractTemplateViewResolver
045: * @see org.springframework.web.servlet.view.velocity.VelocityView
046: * @see org.springframework.web.servlet.view.freemarker.FreeMarkerView
047: */
048: public abstract class AbstractTemplateView extends AbstractUrlBasedView {
049:
050: /**
051: * Variable name of the RequestContext instance in the template model,
052: * available to Spring's macros: e.g. for creating BindStatus objects.
053: */
054: public static final String SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE = "springMacroRequestContext";
055:
056: // Determine whether the Servlet 2.4 HttpServletResponse.getContentType()
057: // method is available.
058: private static boolean responseGetContentTypeAvailable = ClassUtils
059: .hasMethod(HttpServletResponse.class, "getContentType",
060: new Class[0]);
061:
062: private boolean exposeRequestAttributes = false;
063:
064: private boolean allowRequestOverride = false;
065:
066: private boolean exposeSessionAttributes = false;
067:
068: private boolean allowSessionOverride = false;
069:
070: private boolean exposeSpringMacroHelpers = false;
071:
072: /**
073: * Set whether all request attributes should be added to the
074: * model prior to merging with the template. Default is "false".
075: */
076: public void setExposeRequestAttributes(
077: boolean exposeRequestAttributes) {
078: this .exposeRequestAttributes = exposeRequestAttributes;
079: }
080:
081: /**
082: * Set whether HttpServletRequest attributes are allowed to override (hide)
083: * controller generated model attributes of the same name. Default is "false",
084: * which causes an exception to be thrown if request attributes of the same
085: * name as model attributes are found.
086: */
087: public void setAllowRequestOverride(boolean allowRequestOverride) {
088: this .allowRequestOverride = allowRequestOverride;
089: }
090:
091: /**
092: * Set whether all HttpSession attributes should be added to the
093: * model prior to merging with the template. Default is "false".
094: */
095: public void setExposeSessionAttributes(
096: boolean exposeSessionAttributes) {
097: this .exposeSessionAttributes = exposeSessionAttributes;
098: }
099:
100: /**
101: * Set whether HttpSession attributes are allowed to override (hide)
102: * controller generated model attributes of the same name. Default is "false",
103: * which causes an exception to be thrown if session attributes of the same
104: * name as model attributes are found.
105: */
106: public void setAllowSessionOverride(boolean allowSessionOverride) {
107: this .allowSessionOverride = allowSessionOverride;
108: }
109:
110: /**
111: * Set whether to expose a RequestContext for use by Spring's macro library,
112: * under the name "springMacroRequestContext". Default is "false".
113: * <p>Currently needed for Spring's Velocity and FreeMarker default macros.
114: * Note that this is <i>not</i> required for templates that use HTML
115: * forms <i>unless</i> you wish to take advantage of the Spring helper macros.
116: * @see #SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE
117: */
118: public void setExposeSpringMacroHelpers(
119: boolean exposeSpringMacroHelpers) {
120: this .exposeSpringMacroHelpers = exposeSpringMacroHelpers;
121: }
122:
123: protected final void renderMergedOutputModel(Map model,
124: HttpServletRequest request, HttpServletResponse response)
125: throws Exception {
126:
127: if (this .exposeRequestAttributes) {
128: for (Enumeration en = request.getAttributeNames(); en
129: .hasMoreElements();) {
130: String attribute = (String) en.nextElement();
131: if (model.containsKey(attribute)
132: && !this .allowRequestOverride) {
133: throw new ServletException(
134: "Cannot expose request attribute '"
135: + attribute
136: + "' because of an existing model object of the same name");
137: }
138: Object attributeValue = request.getAttribute(attribute);
139: if (logger.isDebugEnabled()) {
140: logger.debug("Exposing request attribute '"
141: + attribute + "' with value ["
142: + attributeValue + "] to model");
143: }
144: model.put(attribute, attributeValue);
145: }
146: }
147:
148: if (this .exposeSessionAttributes) {
149: HttpSession session = request.getSession(false);
150: if (session != null) {
151: for (Enumeration en = session.getAttributeNames(); en
152: .hasMoreElements();) {
153: String attribute = (String) en.nextElement();
154: if (model.containsKey(attribute)
155: && !this .allowSessionOverride) {
156: throw new ServletException(
157: "Cannot expose session attribute '"
158: + attribute
159: + "' because of an existing model object of the same name");
160: }
161: Object attributeValue = session
162: .getAttribute(attribute);
163: if (logger.isDebugEnabled()) {
164: logger.debug("Exposing session attribute '"
165: + attribute + "' with value ["
166: + attributeValue + "] to model");
167: }
168: model.put(attribute, attributeValue);
169: }
170: }
171: }
172:
173: if (this .exposeSpringMacroHelpers) {
174: if (model
175: .containsKey(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE)) {
176: throw new ServletException(
177: "Cannot expose bind macro helper '"
178: + SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE
179: + "' because of an existing model object of the same name");
180: }
181: // Expose RequestContext instance for Spring macros.
182: model.put(SPRING_MACRO_REQUEST_CONTEXT_ATTRIBUTE,
183: new RequestContext(request, getServletContext(),
184: model));
185: }
186:
187: applyContentType(response);
188:
189: renderMergedTemplateModel(model, request, response);
190: }
191:
192: /**
193: * Apply this view's content type as specified in the "contentType"
194: * bean property to the given response.
195: * <p>When running on Servlet 2.4, only applies the view's contentType
196: * if no content type has been set on the response before. This allows
197: * handlers to override the default content type beforehand.
198: * @param response current HTTP response
199: * @see #setContentType
200: */
201: protected void applyContentType(HttpServletResponse response) {
202: if (!responseGetContentTypeAvailable
203: || response.getContentType() == null) {
204: response.setContentType(getContentType());
205: }
206: }
207:
208: /**
209: * Subclasses must implement this method to actually render the view.
210: * @param model combined output Map, with request attributes and
211: * session attributes merged into it if required
212: * @param request current HTTP request
213: * @param response current HTTP response
214: * @throws Exception if rendering failed
215: */
216: protected abstract void renderMergedTemplateModel(Map model,
217: HttpServletRequest request, HttpServletResponse response)
218: throws Exception;
219:
220: }
|