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