1 /*
2 * $Id: TilesRequestProcessor.java 164703 2005-04-26 01:28:13Z niallp $
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> as a definition name.
113 * It returns <code>true</code> if a definition has been processed, or <code>false</code> otherwise.
114 * Parameter <code>contextRelative</code> is not used in this implementation.
115 *
116 * @param definitionName Definition name to insert.
117 * @param contextRelative Is the definition marked contextRelative ?
118 * @param request Current page request.
119 * @param response Current page response.
120 * @return <code>true</code> if the method has processed uri as a definition name, <code>false</code> otherwise.
121 */
122 protected boolean processTilesDefinition(
123 String definitionName,
124 boolean contextRelative,
125 HttpServletRequest request,
126 HttpServletResponse response)
127 throws IOException, ServletException {
128
129 // Do we do a forward (original behavior) or an include ?
130 boolean doInclude = false;
131
132 // Controller associated to a definition, if any
133 Controller controller = null;
134
135 // Computed uri to include
136 String uri = null;
137
138 ComponentContext tileContext = null;
139
140 try {
141 // Get current tile context if any.
142 // If context exist, we will do an include
143 tileContext = ComponentContext.getContext(request);
144 doInclude = (tileContext != null);
145 ComponentDefinition definition = null;
146
147 // Process tiles definition names only if a definition factory exist,
148 // and definition is found.
149 if (definitionsFactory != null) {
150 // Get definition of tiles/component corresponding to uri.
151 try {
152 definition =
153 definitionsFactory.getDefinition(
154 definitionName,
155 request,
156 getServletContext());
157 } catch (NoSuchDefinitionException ex) {
158 // Ignore not found
159 log.debug("NoSuchDefinitionException " + ex.getMessage());
160 }
161 if (definition != null) { // We have a definition.
162 // We use it to complete missing attribute in context.
163 // We also get uri, controller.
164 uri = definition.getPath();
165 controller = definition.getOrCreateController();
166
167 if (tileContext == null) {
168 tileContext =
169 new ComponentContext(definition.getAttributes());
170 ComponentContext.setContext(tileContext, request);
171
172 } else {
173 tileContext.addMissing(definition.getAttributes());
174 }
175 }
176 }
177
178 // Process definition set in Action, if any.
179 definition = DefinitionsUtil.getActionDefinition(request);
180 if (definition != null) { // We have a definition.
181 // We use it to complete missing attribute in context.
182 // We also overload uri and controller if set in definition.
183 if (definition.getPath() != null) {
184 uri = definition.getPath();
185 }
186
187 if (definition.getOrCreateController() != null) {
188 controller = definition.getOrCreateController();
189 }
190
191 if (tileContext == null) {
192 tileContext =
193 new ComponentContext(definition.getAttributes());
194 ComponentContext.setContext(tileContext, request);
195 } else {
196 tileContext.addMissing(definition.getAttributes());
197 }
198 }
199
200 } catch (java.lang.InstantiationException ex) {
201
202 log.error("Can't create associated controller", ex);
203
204 throw new ServletException(
205 "Can't create associated controller",
206 ex);
207 } catch (DefinitionsFactoryException ex) {
208 throw new ServletException(ex);
209 }
210
211 // Have we found a definition ?
212 if (uri == null) {
213 return false;
214 }
215
216 // Execute controller associated to definition, if any.
217 if (controller != null) {
218 try {
219 controller.execute(
220 tileContext,
221 request,
222 response,
223 getServletContext());
224
225 } catch (Exception e) {
226 throw new ServletException(e);
227 }
228 }
229
230 // If request comes from a previous Tile, do an include.
231 // This allows to insert an action in a Tile.
232 if (log.isDebugEnabled()) {
233 log.debug("uri=" + uri + " doInclude=" + doInclude);
234 }
235
236 if (doInclude) {
237 doInclude(uri, request, response);
238 } else {
239 doForward(uri, request, response); // original behavior
240 }
241
242 return true;
243 }
244
245 /**
246 * Do a forward using request dispatcher.
247 * Uri is a valid uri. If response has already been commited, do an include
248 * instead.
249 * @param uri Uri or Definition name to forward.
250 * @param request Current page request.
251 * @param response Current page response.
252 */
253 protected void doForward(
254 String uri,
255 HttpServletRequest request,
256 HttpServletResponse response)
257 throws IOException, ServletException {
258
259 if (response.isCommitted()) {
260 this.doInclude(uri, request, response);
261
262 } else {
263 super.doForward(uri, request, response);
264 }
265 }
266
267 /**
268 * Overloaded method from Struts' RequestProcessor.
269 * Forward or redirect to the specified destination by the specified
270 * mechanism.
271 * This method catches the Struts' actionForward call. It checks if the
272 * actionForward is done on a Tiles definition name. If true, process the
273 * definition and insert it. If false, call the original parent's method.
274 * @param request The servlet request we are processing.
275 * @param response The servlet response we are creating.
276 * @param forward The ActionForward controlling where we go next.
277 *
278 * @exception IOException if an input/output error occurs.
279 * @exception ServletException if a servlet exception occurs.
280 */
281 protected void processForwardConfig(
282 HttpServletRequest request,
283 HttpServletResponse response,
284 ForwardConfig forward)
285 throws IOException, ServletException {
286
287 // Required by struts contract
288 if (forward == null) {
289 return;
290 }
291
292 if (log.isDebugEnabled()) {
293 log.debug(
294 "processForwardConfig("
295 + forward.getPath()
296 + ", "
297 + forward.getContextRelative()
298 + ")");
299 }
300
301 // Try to process the definition.
302 if (processTilesDefinition(forward.getPath(),
303 forward.getContextRelative(),
304 request,
305 response)) {
306 if (log.isDebugEnabled()) {
307 log.debug(
308 " '" + forward.getPath() + "' - processed as definition");
309 }
310 return;
311 }
312
313 if (log.isDebugEnabled()) {
314 log.debug(" '" + forward.getPath() + "' - processed as uri");
315 }
316
317 // forward doesn't contain a definition, let parent do processing
318 super.processForwardConfig(request, response, forward);
319 }
320
321 /**
322 * Catch the call to a module relative forward.
323 * If the specified uri is a tiles definition name, insert it.
324 * Otherwise, parent processing is called.
325 * Do a module relative forward to specified uri using request dispatcher.
326 * Uri is relative to the current module. The real uri is computed by
327 * prefixing the module name.
328 * <strong>This method is used internally and is not part of the public
329 * API. It is advised to not use it in subclasses.</strong>
330 * @param uri Module-relative URI to forward to.
331 * @param request Current page request.
332 * @param response Current page response.
333 * @since Struts 1.1
334 */
335 protected void internalModuleRelativeForward(
336 String uri,
337 HttpServletRequest request,
338 HttpServletResponse response)
339 throws IOException, ServletException {
340
341 if (processTilesDefinition(uri, false, request, response)) {
342 return;
343 }
344
345 super.internalModuleRelativeForward(uri, request, response);
346 }
347
348 /**
349 * Do a module relative include to specified uri using request dispatcher.
350 * Uri is relative to the current module. The real uri is computed by
351 * prefixing the module name.
352 * <strong>This method is used internally and is not part of the public
353 * API. It is advised to not use it in subclasses.</strong>
354 * @param uri Module-relative URI to forward to.
355 * @param request Current page request.
356 * @param response Current page response.
357 * @since Struts 1.1
358 */
359 protected void internalModuleRelativeInclude(
360 String uri,
361 HttpServletRequest request,
362 HttpServletResponse response)
363 throws IOException, ServletException {
364
365 if (processTilesDefinition(uri, false, request, response)) {
366 return;
367 }
368
369 super.internalModuleRelativeInclude(uri, request, response);
370 }
371
372 /**
373 * Get associated definition factory.
374 */
375 public DefinitionsFactory getDefinitionsFactory() {
376 return definitionsFactory;
377 }
378
379 }