View Javadoc

1   /*
2    * Copyright 2007-2012 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.springbyexample.web.servlet.view.tiles2;
18  
19  import javax.servlet.ServletContext;
20  import javax.servlet.ServletRequest;
21  import javax.servlet.http.HttpServletRequest;
22  import javax.servlet.http.HttpServletResponse;
23  
24  import org.apache.tiles.Attribute;
25  import org.apache.tiles.AttributeContext;
26  import org.apache.tiles.TilesContainer;
27  import org.apache.tiles.TilesException;
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.util.StringUtils;
31  import org.springframework.web.servlet.support.JstlUtils;
32  import org.springframework.web.servlet.support.RequestContext;
33  import org.springframework.web.util.WebUtils;
34  
35  /**
36   * <p>Used for rendering and processing a dynamic tiles view.</p>
37   *
38   * @author David Winterfeldt
39   */
40  public class DynamicTilesViewProcessor {
41  
42      final Logger logger = LoggerFactory.getLogger(DynamicTilesViewProcessor.class);
43  
44  	/**
45  	 * Keeps Tiles definition to use once derived.
46  	 */
47  	private String derivedDefinitionName = null;
48  
49  	private String tilesDefinitionName = "mainTemplate";
50  	private String tilesBodyAttributeName = "content";
51  	private String tilesDefinitionDelimiter = ".";
52  
53  	/**
54  	 * Main template name.  The default is 'mainTemplate'.
55  	 *
56  	 * @param 	tilesDefinitionName		Main template name used to lookup definitions.
57  	 */
58  	public void setTilesDefinitionName(String tilesDefinitionName) {
59  		this.tilesDefinitionName = tilesDefinitionName;
60  	}
61  
62  	/**
63  	 * Tiles body attribute name.  The default is 'body'.
64  	 *
65  	 * @param 	tilesBodyAttributeName		Tiles body attribute name.
66  	 */
67  	public void setTilesBodyAttributeName(String tilesBodyAttributeName) {
68  		this.tilesBodyAttributeName = tilesBodyAttributeName;
69  	}
70  
71  	/**
72  	 * Sets Tiles definition delimiter.  For example, instead of using
73  	 * the request 'info/about' to lookup the template definition
74  	 * 'info/mainTemplate', the default delimiter of '.'
75  	 * would look for '.info.mainTemplate'
76  	 *
77  	 * @param 	tilesDefinitionDelimiter	Optional delimiter to replace '/' in a url.
78  	 */
79  	public void setTilesDefinitionDelimiter(String tilesDefinitionDelimiter) {
80  		this.tilesDefinitionDelimiter = tilesDefinitionDelimiter;
81  	}
82  
83  	/**
84  	 * Renders output using Tiles.
85  	 */
86  	protected void renderMergedOutputModel(String beanName, String url,
87  	                                       ServletContext servletContext,
88  	                                       HttpServletRequest request, HttpServletResponse response,
89  	                                       TilesContainer container)
90  	       throws Exception {
91          JstlUtils.exposeLocalizationContext(new RequestContext(request, servletContext));
92  
93          if (!response.isCommitted()) {
94              // Tiles is going to use a forward, but some web containers (e.g.
95              // OC4J 10.1.3)
96              // do not properly expose the Servlet 2.4 forward request
97              // attributes... However,
98              // must not do this on Servlet 2.5 or above, mainly for GlassFish
99              // compatibility.
100             if (servletContext.getMajorVersion() == 2 && servletContext.getMinorVersion() < 5) {
101                 exposeForwardRequestAttributes(request);
102             }
103         }
104 
105         String definitionName = startDynamicDefinition(beanName, url, request, response, container);
106 
107         container.render(definitionName, request, response);
108 
109         endDynamicDefinition(definitionName, beanName, request, response, container);
110     }
111 
112 	/**
113 	 * Starts processing the dynamic Tiles definition by creating a temporary definition for rendering.
114 	 */
115 	protected String startDynamicDefinition(String beanName, String url,
116                                             HttpServletRequest request, HttpServletResponse response,
117                                             TilesContainer container)
118 	        throws TilesException {
119        String definitionName = processTilesDefinitionName(beanName, container,
120                                                           request, response);
121 
122         // create a temporary context and render using the incoming url as the
123         // body attribute
124         if (!definitionName.equals(beanName)) {
125             Attribute attr = new Attribute();
126             attr.setName(tilesBodyAttributeName);
127             attr.setValue(url);
128 
129             AttributeContext attributeContext = container.startContext(request, response);
130             attributeContext.putAttribute(tilesBodyAttributeName, attr);
131 
132             logger.debug("URL used for Tiles body.  url='" + url + "'.");
133         }
134 
135         return definitionName;
136 	}
137 
138 	/**
139 	 * Closes the temporary Tiles definition.
140 	 */
141 	protected void endDynamicDefinition(String definitionName, String beanName,
142 	                                    HttpServletRequest request, HttpServletResponse response,
143 	                                    TilesContainer container) {
144         if (!definitionName.equals(beanName)) {
145             container.endContext(request, response);
146         }
147 	}
148 
149 	/**
150 	 * Processes values to get tiles template definition name.  First
151 	 * a Tiles definition matching the url is checked, then a
152 	 * url specific template is checked, and then just the
153 	 * default root definition is used.
154 	 *
155 	 * @throws 	TilesException		If no valid Tiles definition is found.
156 	 */
157 	protected String processTilesDefinitionName(String beanName,
158 	                                            TilesContainer container,
159 												HttpServletRequest request,
160 												HttpServletResponse response)
161 			throws TilesException {
162 		// if definition already derived use it, otherwise
163 		// check if url (bean name) is a template definition, then
164 		// check for main template
165 		if (derivedDefinitionName != null) {
166 			return derivedDefinitionName;
167 		} else if (container.isValidDefinition(beanName, request, response)) {
168 			derivedDefinitionName = beanName;
169 
170 			return beanName;
171 		} else {
172 			String result = null;
173 
174 			StringBuilder sb = new StringBuilder();
175 			int lastIndex = beanName.lastIndexOf("/");
176 			boolean rootDefinition = false;
177 
178 			// if delim, tiles def will start with it
179 			if (StringUtils.hasLength(tilesDefinitionDelimiter)) {
180 				sb.append(tilesDefinitionDelimiter);
181 			}
182 
183 			// if no '/', then at context root
184 			if (lastIndex == -1) {
185 				rootDefinition = true;
186 			} else {
187 				String path = (beanName != null ? beanName.substring(0, lastIndex) : "");
188 
189 				if (StringUtils.hasLength(tilesDefinitionDelimiter)) {
190 					path = StringUtils.replace(path, "/", tilesDefinitionDelimiter);
191 
192 				}
193 
194 				sb.append(path);
195 
196 				if (StringUtils.hasLength(tilesDefinitionDelimiter)) {
197 					sb.append(tilesDefinitionDelimiter);
198 				}
199 			}
200 
201 			sb.append(tilesDefinitionName);
202 
203 			if (container.isValidDefinition(sb.toString(), request, response)) {
204 				result = sb.toString();
205 			} else if (!rootDefinition) {
206 				String root = null;
207 
208 				if (StringUtils.hasLength(tilesDefinitionDelimiter)) {
209 					root = tilesDefinitionDelimiter;
210 				}
211 
212 				root += tilesDefinitionName;
213 
214 				if (container.isValidDefinition(root, request, response)) {
215 					result = root;
216 				} else {
217 					throw new TilesException("No defintion of found for " +
218 							"'" + root +"'" +
219 							" or '" + sb.toString() +"'");
220 				}
221 			}
222 
223 			derivedDefinitionName = result;
224 
225 			return result;
226 		}
227 	}
228 
229     /**
230      * Expose the current request URI and paths as {@link javax.servlet.http.HttpServletRequest}
231      * attributes under the keys defined in the Servlet 2.4 specification,
232      * for containers that implement 2.3 or an earlier version of the Servlet API:
233      * {@code javax.servlet.forward.request_uri},
234      * {@code javax.servlet.forward.context_path},
235      * {@code javax.servlet.forward.servlet_path},
236      * {@code javax.servlet.forward.path_info},
237      * {@code javax.servlet.forward.query_string}.
238      * <p>Does not override values if already present, to not cause conflicts
239      * with the attributes exposed by Servlet 2.4+ containers themselves.</p>
240      *
241      * <p><strong>Note:</strong> From Spring <code>WebUtils</code> 3.x.</p>
242      *
243      * @param request current servlet request
244      */
245     public void exposeForwardRequestAttributes(HttpServletRequest request) {
246         exposeRequestAttributeIfNotPresent(request, WebUtils.FORWARD_REQUEST_URI_ATTRIBUTE, request.getRequestURI());
247         exposeRequestAttributeIfNotPresent(request, WebUtils.FORWARD_CONTEXT_PATH_ATTRIBUTE, request.getContextPath());
248         exposeRequestAttributeIfNotPresent(request, WebUtils.FORWARD_SERVLET_PATH_ATTRIBUTE, request.getServletPath());
249         exposeRequestAttributeIfNotPresent(request, WebUtils.FORWARD_PATH_INFO_ATTRIBUTE, request.getPathInfo());
250         exposeRequestAttributeIfNotPresent(request, WebUtils.FORWARD_QUERY_STRING_ATTRIBUTE, request.getQueryString());
251     }
252 
253     /**
254      * Expose the specified request attribute if not already present.
255      */
256     private void exposeRequestAttributeIfNotPresent(ServletRequest request, String name, Object value) {
257         if (request.getAttribute(name) == null) {
258             request.setAttribute(name, value);
259         }
260     }
261 }