View Javadoc

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