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 }