View Javadoc

1   /*
2    * $Id: TilesRequestProcessor.java 232641 2005-08-14 20:51:24Z hrabago $ 
3    *
4    * Copyright 1999-2005 The Apache Software Foundation.
5    * 
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    * 
10   *      http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.apache.struts.tiles;
20  
21  import java.io.IOException;
22  
23  import javax.servlet.ServletException;
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  import org.apache.struts.action.ActionServlet;
30  import org.apache.struts.action.RequestProcessor;
31  import org.apache.struts.config.ForwardConfig;
32  import org.apache.struts.config.ModuleConfig;
33  
34  /***
35   * <p><strong>RequestProcessor</strong> contains the processing logic that
36   * the Struts controller servlet performs as it receives each servlet request
37   * from the container.</p>
38   * <p>This processor subclasses the Struts RequestProcessor in order to intercept calls to forward
39   * or include. When such calls are done, the Tiles processor checks if the specified URI
40   * is a definition name. If true, the definition is retrieved and included. If
41   * false, the original URI is included or a forward is performed.
42   * <p>
43   * Actually, catching is done by overloading the following methods:
44   * <ul>
45   * <li>{@link #processForwardConfig(HttpServletRequest,HttpServletResponse,ForwardConfig)}</li>
46   * <li>{@link #internalModuleRelativeForward(String, HttpServletRequest , HttpServletResponse)}</li>
47   * <li>{@link #internalModuleRelativeInclude(String, HttpServletRequest , HttpServletResponse)}</li>
48   * </ul>
49   * </p>
50   * @since Struts 1.1
51   */
52  public class TilesRequestProcessor extends RequestProcessor {
53  
54  	/*** 
55  	 * Definitions factory. 
56  	 */
57  	protected DefinitionsFactory definitionsFactory = null;
58  
59  	/***
60  	 * Commons Logging instance.
61  	 */
62  	protected static Log log = LogFactory.getLog(TilesRequestProcessor.class);
63  
64  	/***
65  	 * Initialize this request processor instance.
66  	 *
67  	 * @param servlet The ActionServlet we are associated with.
68  	 * @param moduleConfig The ModuleConfig we are associated with.
69  	 * @throws ServletException If an error occurs during initialization.
70  	 */
71  	public void init(ActionServlet servlet, ModuleConfig moduleConfig)
72  		throws ServletException {
73  
74  		super.init(servlet, moduleConfig);
75  		this.initDefinitionsMapping();
76  	}
77  
78  	/***
79  	 * Read component instance mapping configuration file.
80  	 * This is where we read files properties.
81  	 */
82  	protected void initDefinitionsMapping() throws ServletException {
83  		// Retrieve and set factory for this modules
84  		definitionsFactory =
85  			(
86  				(TilesUtilStrutsImpl) TilesUtil
87  					.getTilesUtil())
88  					.getDefinitionsFactory(
89  				getServletContext(),
90  				moduleConfig);
91  
92  		if (definitionsFactory == null) { // problem !
93  
94  			log.info(
95  				"Definition Factory not found for module '"
96  					+ moduleConfig.getPrefix()
97  					+ "'. "
98  					+ "Have you declared the appropriate plugin in struts-config.xml ?");
99  
100 			return;
101 		}
102 
103 		log.info(
104 			"Tiles definition factory found for request processor '"
105 				+ moduleConfig.getPrefix()
106 				+ "'.");
107 
108 	}
109 
110 	/***
111 	 * Process a Tile definition name.
112 	 * This method tries to process the parameter <code>definitionName</code> 
113      * as a definition name.
114 	 * It returns <code>true</code> if a definition has been processed, or 
115      * <code>false</code> otherwise.
116 	 * This method is deprecated; the method without the 
117      * <code>contextRelative</code> parameter should be used instead.
118 	 *
119 	 * @param definitionName Definition name to insert.
120 	 * @param contextRelative Is the definition marked contextRelative ?
121 	 * @param request Current page request.
122 	 * @param response Current page response.
123 	 * @return <code>true</code> if the method has processed uri as a 
124      * definition name, <code>false</code> otherwise.
125      * @deprecated use processTilesDefinition(definitionName, request, response)
126      *  instead.  This method will be removed in a version after 1.3.0.
127 	 */
128 	protected boolean processTilesDefinition(
129 		String definitionName,
130 		boolean contextRelative,
131 		HttpServletRequest request,
132 		HttpServletResponse response)
133 		throws IOException, ServletException {
134 
135         return processTilesDefinition(definitionName, request, response);
136         
137     }
138     
139 	/***
140 	 * Process a Tile definition name.
141 	 * This method tries to process the parameter <code>definitionName</code> 
142      * as a definition name.
143 	 * It returns <code>true</code> if a definition has been processed, or 
144      * <code>false</code> otherwise.
145 	 *
146 	 * @param definitionName Definition name to insert.
147 	 * @param request Current page request.
148 	 * @param response Current page response.
149 	 * @return <code>true</code> if the method has processed uri as a 
150      * definition name, <code>false</code> otherwise.
151 	 */
152 	protected boolean processTilesDefinition(
153 		String definitionName,
154 		HttpServletRequest request,
155 		HttpServletResponse response)
156 		throws IOException, ServletException {
157 
158 		// Do we do a forward (original behavior) or an include ?
159 		boolean doInclude = false;
160 
161 		// Controller associated to a definition, if any
162 		Controller controller = null;
163 
164 		// Computed uri to include
165 		String uri = null;
166 
167 		ComponentContext tileContext = null;
168 
169 		try {
170 			// Get current tile context if any.
171 			// If context exist, we will do an include
172 			tileContext = ComponentContext.getContext(request);
173 			doInclude = (tileContext != null);
174 			ComponentDefinition definition = null;
175 
176 			// Process tiles definition names only if a definition factory exist,
177 			// and definition is found.
178 			if (definitionsFactory != null) {
179 				// Get definition of tiles/component corresponding to uri.
180 				try {
181 					definition =
182 						definitionsFactory.getDefinition(
183 							definitionName,
184 							request,
185 							getServletContext());
186 				} catch (NoSuchDefinitionException ex) {
187 					// Ignore not found
188 					log.debug("NoSuchDefinitionException " + ex.getMessage());
189 				}
190 				if (definition != null) { // We have a definition.
191 					// We use it to complete missing attribute in context.
192 					// We also get uri, controller.
193 					uri = definition.getPath();
194 					controller = definition.getOrCreateController();
195 
196 					if (tileContext == null) {
197 						tileContext =
198 							new ComponentContext(definition.getAttributes());
199 						ComponentContext.setContext(tileContext, request);
200 
201 					} else {
202 						tileContext.addMissing(definition.getAttributes());
203 					}
204 				}
205 			}
206 
207 			// Process definition set in Action, if any.
208 			definition = DefinitionsUtil.getActionDefinition(request);
209 			if (definition != null) { // We have a definition.
210 				// We use it to complete missing attribute in context.
211 				// We also overload uri and controller if set in definition.
212 				if (definition.getPath() != null) {
213 					uri = definition.getPath();
214 				}
215 
216 				if (definition.getOrCreateController() != null) {
217 					controller = definition.getOrCreateController();
218 				}
219 
220 				if (tileContext == null) {
221 					tileContext =
222 						new ComponentContext(definition.getAttributes());
223 					ComponentContext.setContext(tileContext, request);
224 				} else {
225 					tileContext.addMissing(definition.getAttributes());
226 				}
227 			}
228 
229 		} catch (java.lang.InstantiationException ex) {
230 
231 			log.error("Can't create associated controller", ex);
232 
233 			throw new ServletException(
234 				"Can't create associated controller",
235 				ex);
236 		} catch (DefinitionsFactoryException ex) {
237 			throw new ServletException(ex);
238 		}
239 
240 		// Have we found a definition ?
241 		if (uri == null) {
242 			return false;
243 		}
244 
245 		// Execute controller associated to definition, if any.
246 		if (controller != null) {
247 			try {
248 				controller.execute(
249 					tileContext,
250 					request,
251 					response,
252 					getServletContext());
253                     
254 			} catch (Exception e) {
255 				throw new ServletException(e); 
256 			}
257 		}
258 
259 		// If request comes from a previous Tile, do an include.
260 		// This allows to insert an action in a Tile.
261 		if (log.isDebugEnabled()) {
262 			log.debug("uri=" + uri + " doInclude=" + doInclude);
263 		}
264         
265 		if (doInclude) {
266 			doInclude(uri, request, response);
267 		} else {
268 			doForward(uri, request, response); // original behavior
269 		}
270 
271 		return true;
272 	}
273 
274 	/***
275 	 * Do a forward using request dispatcher.
276 	 * Uri is a valid uri. If response has already been commited, do an include
277 	 * instead.
278 	 * @param uri Uri or Definition name to forward.
279 	 * @param request Current page request.
280 	 * @param response Current page response.
281 	 */
282 	protected void doForward(
283 		String uri,
284 		HttpServletRequest request,
285 		HttpServletResponse response)
286 		throws IOException, ServletException {
287             
288 		if (response.isCommitted()) {
289 			this.doInclude(uri, request, response);
290             
291 		} else {
292 			super.doForward(uri, request, response);
293 		}
294 	}
295 
296 	/***
297 	 * Overloaded method from Struts' RequestProcessor.
298 	 * Forward or redirect to the specified destination by the specified
299 	 * mechanism.
300 	 * This method catches the Struts' actionForward call. It checks if the
301 	 * actionForward is done on a Tiles definition name. If true, process the
302 	 * definition and insert it. If false, call the original parent's method.
303 	 * @param request The servlet request we are processing.
304 	 * @param response The servlet response we are creating.
305 	 * @param forward The ActionForward controlling where we go next.
306 	 *
307 	 * @exception IOException if an input/output error occurs.
308 	 * @exception ServletException if a servlet exception occurs.
309 	 */
310 	protected void processForwardConfig(
311 		HttpServletRequest request,
312 		HttpServletResponse response,
313 		ForwardConfig forward)
314 		throws IOException, ServletException {
315             
316 		// Required by struts contract
317 		if (forward == null) {
318 			return;
319 		}
320 
321 		if (log.isDebugEnabled()) {
322 			log.debug(
323 				"processForwardConfig("
324 					+ forward.getPath()
325 					+ ")");
326 		}
327         
328 		// Try to process the definition.
329 		if (processTilesDefinition(forward.getPath(),
330 			request,
331 			response)) {
332 			if (log.isDebugEnabled()) {
333 				log.debug(
334 					"  '" + forward.getPath() + "' - processed as definition");
335 			}
336 			return;
337 		}
338         
339 		if (log.isDebugEnabled()) {
340 			log.debug("  '" + forward.getPath() + "' - processed as uri");
341 		}
342         
343 		// forward doesn't contain a definition, let parent do processing
344 		super.processForwardConfig(request, response, forward);
345 	}
346 
347 	/***
348 	 * Catch the call to a module relative forward.
349 	 * If the specified uri is a tiles definition name, insert it.
350 	 * Otherwise, parent processing is called.
351 	 * Do a module relative forward to specified uri using request dispatcher.
352 	 * Uri is relative to the current module. The real uri is computed by 
353      * prefixing the module name.
354 	 * <strong>This method is used internally and is not part of the public 
355      * API. It is advised to not use it in subclasses.</strong>
356 	 * @param uri Module-relative URI to forward to.
357 	 * @param request Current page request.
358 	 * @param response Current page response.
359 	 * @since Struts 1.1
360 	 */
361 	protected void internalModuleRelativeForward(
362 		String uri,
363 		HttpServletRequest request,
364 		HttpServletResponse response)
365 		throws IOException, ServletException {
366             
367 		if (processTilesDefinition(uri, request, response)) {
368 			return;
369 		}
370 
371 		super.internalModuleRelativeForward(uri, request, response);
372 	}
373 
374 	/***
375 	 * Do a module relative include to specified uri using request dispatcher.
376 	 * Uri is relative to the current module. The real uri is computed by 
377      * prefixing the module name.
378 	 * <strong>This method is used internally and is not part of the public 
379      * API. It is advised to not use it in subclasses.</strong>
380 	 * @param uri Module-relative URI to forward to.
381 	 * @param request Current page request.
382 	 * @param response Current page response.
383 	 * @since Struts 1.1
384 	 */
385 	protected void internalModuleRelativeInclude(
386 		String uri,
387 		HttpServletRequest request,
388 		HttpServletResponse response)
389 		throws IOException, ServletException {
390             
391 		if (processTilesDefinition(uri, request, response)) {
392 			return;
393 		}
394 
395 		super.internalModuleRelativeInclude(uri, request, response);
396 	}
397 
398 	/***
399 	 * Get associated definition factory.
400 	 */
401 	public DefinitionsFactory getDefinitionsFactory() {
402 		return definitionsFactory;
403 	}
404 
405 }