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  package org.apache.struts.tiles.commands;
22  
23  import java.io.IOException;
24  
25  import jakarta.servlet.RequestDispatcher;
26  import jakarta.servlet.ServletException;
27  import jakarta.servlet.http.HttpServletResponse;
28  
29  import org.apache.commons.chain.Command;
30  import org.apache.struts.chain.contexts.ServletActionContext;
31  import org.apache.struts.config.ForwardConfig;
32  import org.apache.struts.tiles.ComponentContext;
33  import org.apache.struts.tiles.ComponentDefinition;
34  import org.apache.struts.tiles.Controller;
35  import org.apache.struts.tiles.FactoryNotFoundException;
36  import org.apache.struts.tiles.NoSuchDefinitionException;
37  import org.apache.struts.tiles.TilesUtil;
38  import org.slf4j.Logger;
39  import org.slf4j.LoggerFactory;
40  
41  /**
42   * Command class intended to perform responsibilities of the
43   * TilesRequestProcessor in Struts 1.1. Does not actually dispatch requests,
44   * but simply prepares the chain context for a later forward as appropriate.
45   * Should be added to a chain before something which would handle a
46   * conventional ForwardConfig.
47   *
48   * <p>This class will never have any effect on the chain unless a
49   * {@code TilesDefinitionFactory} can be found; however it does not consider
50   * the absence of a definition factory to be a fatal error; the command simply
51   * returns false and lets the chain continue.</p>
52   *
53   * <p>To initialize the {@code TilesDefinitionFactory}, use
54   * {@code org.apache.struts.chain.commands.legacy.TilesPlugin}. This class is a
55   * simple extension to {@code org.apache.struts.tiles.TilesPlugin} which simply
56   * does not interfere with your choice of {@code RequestProcessor}
57   * implementation.</p>
58   */
59  public class TilesPreProcessor implements Command<ServletActionContext>
60  {
61  
62  
63      // ------------------------------------------------------ Instance Variables
64  
65  
66      /**
67       * The {@code Log} instance for this class.
68       */
69      private final Logger log =
70          LoggerFactory.getLogger(TilesPreProcessor.class);
71  
72      // ---------------------------------------------------------- Public Methods
73  
74  
75      /**
76       * If the current {@code ForwardConfig} is using "tiles", perform necessary
77       * pre-processing to set up the {@code TilesContext} and substitute a new
78       * {@code ForwardConfig} which is understandable to a
79       * {@code RequestDispatcher}.
80       *
81       * <p>Note that if the command finds a previously existing
82       * {@code ComponentContext} in the request, then it infers that it has been
83       * called from within another tile, so instead of changing the
84       * {@code ForwardConfig} in the chain {@code Context}, the command uses
85       * {@code RequestDispatcher} to <em>include</em> the tile, and returns
86       * true, indicating that the processing chain is complete.</p>
87       *
88       * @param sacontext The {@code Context} for the current request
89       *
90       * @return {@code false} in most cases, but true if we determine that we're
91       *         processing in "include" mode.
92       */
93      @SuppressWarnings("deprecation")
94      public boolean execute(ServletActionContext sacontext) throws Exception {
95  
96          // Is there a Tiles Definition to be processed?
97          ForwardConfig forwardConfig = sacontext.getForwardConfig();
98          if (forwardConfig == null || forwardConfig.getPath() == null)
99          {
100             log.debug("No forwardConfig or no path, so pass to next command.");
101             return (false);
102         }
103 
104 
105         ComponentDefinition definition = null;
106         try
107         {
108             definition = TilesUtil.getDefinition(forwardConfig.getPath(),
109                     sacontext.getRequest(),
110                     sacontext.getContext());
111         }
112         catch (FactoryNotFoundException ex)
113         {
114             // this is not a serious error, so log at low priority
115             log.debug("Tiles DefinitionFactory not found, so pass to next command.");
116             return false;
117         }
118         catch (NoSuchDefinitionException ex)
119         {
120             // ignore not found
121             log.debug("NoSuchDefinitionException {}", ex.getMessage());
122         }
123 
124         // Do we do a forward (original behavior) or an include ?
125         boolean doInclude = false;
126         ComponentContext tileContext = null;
127 
128         // Get current tile context if any.
129         // If context exists, or if the response has already been committed we will do an include
130         tileContext = ComponentContext.getContext(sacontext.getRequest());
131         doInclude = (tileContext != null || sacontext.getResponse().isCommitted());
132 
133         // Controller associated to a definition, if any
134         Controller controller = null;
135 
136         // Computed uri to include
137         String uri = null;
138 
139         if (definition != null)
140         {
141             // We have a "forward config" definition.
142             // We use it to complete missing attribute in context.
143             // We also get uri, controller.
144             uri = definition.getPath();
145             controller = definition.getOrCreateController();
146 
147             if (tileContext == null) {
148                 tileContext =
149                         new ComponentContext(definition.getAttributes());
150                 ComponentContext.setContext(tileContext, sacontext.getRequest());
151 
152             } else {
153                 tileContext.addMissing(definition.getAttributes());
154             }
155         }
156 
157         // Process definition set in Action, if any.  This may override the
158         // values for uri or controller found using the ForwardConfig, and
159         // may augment the tileContext with additional attributes.
160         // :FIXME: the class DefinitionsUtil is deprecated, but I can't find
161         // the intended alternative to use.
162         definition = org.apache.struts.tiles.DefinitionsUtil.getActionDefinition(sacontext.getRequest());
163         if (definition != null) { // We have a definition.
164                 // We use it to complete missing attribute in context.
165                 // We also overload uri and controller if set in definition.
166                 if (definition.getPath() != null) {
167                     log.debug("Override forward uri {} with action uri {}",
168                         uri, definition.getPath());
169                     uri = definition.getPath();
170                 }
171 
172                 if (definition.getOrCreateController() != null) {
173                     log.debug("Override forward controller with action controller");
174                     controller = definition.getOrCreateController();
175                 }
176 
177                 if (tileContext == null) {
178                         tileContext =
179                                 new ComponentContext(definition.getAttributes());
180                         ComponentContext.setContext(tileContext, sacontext.getRequest());
181                 } else {
182                         tileContext.addMissing(definition.getAttributes());
183                 }
184         }
185 
186 
187         if (uri == null) {
188             log.debug("no uri computed, so pass to next command");
189             return false;
190         }
191 
192         // Execute controller associated to definition, if any.
193         if (controller != null) {
194             log.trace("Execute controller: {}", controller);
195             controller.execute(
196                     tileContext,
197                     sacontext.getRequest(),
198                     sacontext.getResponse(),
199                     sacontext.getContext());
200         }
201 
202         // If request comes from a previous Tile, do an include.
203         // This allows to insert an action in a Tile.
204 
205         if (doInclude) {
206             log.info("Tiles process complete; doInclude with {}", uri);
207             doInclude(sacontext, uri);
208         } else {
209             log.info("Tiles process complete; forward to {}", uri);
210             doForward(sacontext, uri);
211         }
212 
213         log.debug("Tiles processed, so clearing forward config from context.");
214         sacontext.setForwardConfig( null );
215         return (false);
216     }
217 
218 
219     // ------------------------------------------------------- Protected Methods
220 
221     /**
222      * Do an include of specified URI using a {@code RequestDispatcher}.
223      *
224      * @param context a chain servlet/web context
225      * @param uri     Context-relative URI to include
226      */
227     protected void doInclude(
228         ServletActionContext context,
229         String uri)
230         throws IOException, ServletException {
231 
232         RequestDispatcher rd = getRequiredDispatcher(context, uri);
233 
234         if (rd != null) {
235             rd.include(context.getRequest(), context.getResponse());
236         }
237     }
238 
239     /**
240      * Do an include of specified URI using a {@code RequestDispatcher}.
241      *
242      * @param context a chain servlet/web context
243      * @param uri     Context-relative URI to include
244      */
245     protected void doForward(
246         ServletActionContext context,
247         String uri)
248         throws IOException, ServletException {
249 
250         RequestDispatcher rd = getRequiredDispatcher(context, uri);
251 
252         if (rd != null) {
253             rd.forward(context.getRequest(), context.getResponse());
254         }
255     }
256 
257     /**
258      * Get the {@code RequestDispatcher} for the specified {@code uri}. If it
259      * is not found, send a 500 error as a response and return null;
260      *
261      * @param context the current {@code ServletActionContext}
262      * @param uri     the ServletContext-relative URI of the request dispatcher to find.
263      *
264      * @return the {@code RequestDispatcher}, or null if none is returned from the {@code ServletContext}.
265      *
266      * @throws IOException if {@code getRequestDispatcher(uri)} has an error.
267      */
268     private RequestDispatcher getRequiredDispatcher(ServletActionContext context, String uri) throws IOException {
269         RequestDispatcher rd = context.getContext().getRequestDispatcher(uri);
270         if (rd == null) {
271             log.debug("No request dispatcher found for {}", uri);
272             HttpServletResponse response = context.getResponse();
273             response.sendError(
274                 HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
275                 "Error getting RequestDispatcher for " + uri);
276         }
277         return rd;
278     }
279 }