1 /*
2 * $Id: TilesPlugin.java 54929 2004-10-16 16:38:42Z germuska $
3 *
4 * Copyright 1999-2004 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.util.Map;
22
23 import javax.servlet.ServletContext;
24 import javax.servlet.ServletException;
25 import javax.servlet.UnavailableException;
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.PlugIn;
31 import org.apache.struts.action.RequestProcessor;
32 import org.apache.struts.config.ControllerConfig;
33 import org.apache.struts.config.ModuleConfig;
34 import org.apache.struts.config.PlugInConfig;
35 import org.apache.struts.util.RequestUtils;
36
37 /**
38 * Tiles Plugin used to initialize Tiles.
39 * This plugin is to be used with Struts 1.1 in association with
40 * {@link TilesRequestProcessor}.
41 * <br>
42 * This plugin creates one definition factory for each Struts-module. The definition factory
43 * configuration is read first from 'web.xml' (backward compatibility), then it is
44 * overloaded with values found in the plugin property values.
45 * <br>
46 * The plugin changes the Struts configuration by specifying a {@link TilesRequestProcessor} as
47 * request processor. If you want to use your own RequestProcessor,
48 * it should subclass TilesRequestProcessor.
49 * <br>
50 * This plugin can also be used to create one single factory for all modules.
51 * This behavior is enabled by specifying <code>moduleAware=false</code> in each
52 * plugin properties. In this case, the definition factory
53 * configuration file is read by the first Tiles plugin to be initialized. The order is
54 * determined by the order of modules declaration in web.xml. The first module
55 * is always the default one if it exists.
56 * The plugin should be declared in each struts-config.xml file in order to
57 * properly initialize the request processor.
58 * @since Struts 1.1
59 */
60 public class TilesPlugin implements PlugIn {
61
62 /**
63 * Commons Logging instance.
64 */
65 protected static Log log = LogFactory.getLog(TilesPlugin.class);
66
67 /**
68 * Is the factory module aware?
69 */
70 protected boolean moduleAware = false;
71
72 /**
73 * Tiles util implementation classname. This property can be set
74 * by user in the plugin declaration.
75 */
76 protected String tilesUtilImplClassname = null;
77
78 /**
79 * Associated definition factory.
80 */
81 protected DefinitionsFactory definitionFactory = null;
82
83 /**
84 * The plugin config object provided by the ActionServlet initializing
85 * this plugin.
86 */
87 protected PlugInConfig currentPlugInConfigObject=null;
88
89 /**
90 * Get the module aware flag.
91 * @return <code>true</code>: user wants a single factory instance,
92 * <code>false:</code> user wants multiple factory instances (one per module with Struts)
93 */
94 public boolean isModuleAware() {
95 return moduleAware;
96 }
97
98 /**
99 * Set the module aware flag.
100 * This flag is only meaningful if the property <code>tilesUtilImplClassname</code> is not
101 * set.
102 * @param moduleAware <code>true</code>: user wants a single factory instance,
103 * <code>false:</code> user wants multiple factory instances (one per module with Struts)
104 */
105 public void setModuleAware(boolean moduleAware) {
106 this.moduleAware = moduleAware;
107 }
108
109 /**
110 * <p>Receive notification that the specified module is being
111 * started up.</p>
112 *
113 * @param servlet ActionServlet that is managing all the modules
114 * in this web application.
115 * @param moduleConfig ModuleConfig for the module with which
116 * this plugin is associated.
117 *
118 * @exception ServletException if this <code>PlugIn</code> cannot
119 * be successfully initialized.
120 */
121 public void init(ActionServlet servlet, ModuleConfig moduleConfig)
122 throws ServletException {
123
124 // Create factory config object
125 DefinitionsFactoryConfig factoryConfig =
126 readFactoryConfig(servlet, moduleConfig);
127
128 // Set the module name in the config. This name will be used to compute
129 // the name under which the factory is stored.
130 factoryConfig.setFactoryName(moduleConfig.getPrefix());
131
132 // Set RequestProcessor class
133 this.initRequestProcessorClass(moduleConfig);
134
135 this.initTilesUtil();
136
137 this.initDefinitionsFactory(servlet.getServletContext(), moduleConfig, factoryConfig);
138 }
139
140 /**
141 * Set TilesUtil implementation according to properties 'tilesUtilImplClassname'
142 * and 'moduleAware'. These properties are taken into account only once. A
143 * side effect is that only the values set in the first initialized plugin are
144 * effectively taken into account.
145 * @throws ServletException
146 */
147 private void initTilesUtil() throws ServletException {
148
149 if (TilesUtil.isTilesUtilImplSet()) {
150 return;
151 }
152
153 // Check if user has specified a TilesUtil implementation classname or not.
154 // If no implementation is specified, check if user has specified one
155 // shared single factory for all module, or one factory for each module.
156
157 if (this.getTilesUtilImplClassname() == null) {
158
159 if (isModuleAware()) {
160 TilesUtil.setTilesUtil(new TilesUtilStrutsModulesImpl());
161 } else {
162 TilesUtil.setTilesUtil(new TilesUtilStrutsImpl());
163 }
164
165 } else { // A classname is specified for the tilesUtilImp, use it.
166 try {
167 TilesUtilStrutsImpl impl =
168 (TilesUtilStrutsImpl) RequestUtils
169 .applicationClass(getTilesUtilImplClassname())
170 .newInstance();
171 TilesUtil.setTilesUtil(impl);
172
173 } catch (ClassCastException ex) {
174 throw new ServletException(
175 "Can't set TilesUtil implementation to '"
176 + getTilesUtilImplClassname()
177 + "'. TilesUtil implementation should be a subclass of '"
178 + TilesUtilStrutsImpl.class.getName()
179 + "'");
180
181 } catch (Exception ex) {
182 throw new ServletException(
183 "Can't set TilesUtil implementation.",
184 ex);
185 }
186 }
187
188 }
189
190 /**
191 * Initialize the DefinitionsFactory this module will use.
192 * @param servletContext
193 * @param moduleConfig
194 * @param factoryConfig
195 * @throws ServletException
196 */
197 private void initDefinitionsFactory(
198 ServletContext servletContext,
199 ModuleConfig moduleConfig,
200 DefinitionsFactoryConfig factoryConfig)
201 throws ServletException {
202
203 // Check if a factory already exist for this module
204 definitionFactory =
205 ((TilesUtilStrutsImpl) TilesUtil.getTilesUtil()).getDefinitionsFactory(
206 servletContext,
207 moduleConfig);
208
209 if (definitionFactory != null) {
210 log.info(
211 "Factory already exists for module '"
212 + moduleConfig.getPrefix()
213 + "'. The factory found is from module '"
214 + definitionFactory.getConfig().getFactoryName()
215 + "'. No new creation.");
216
217 return;
218 }
219
220 // Create configurable factory
221 try {
222 definitionFactory =
223 TilesUtil.createDefinitionsFactory(
224 servletContext,
225 factoryConfig);
226
227 } catch (DefinitionsFactoryException ex) {
228 log.error(
229 "Can't create Tiles definition factory for module '"
230 + moduleConfig.getPrefix()
231 + "'.");
232
233 throw new ServletException(ex);
234 }
235
236 log.info(
237 "Tiles definition factory loaded for module '"
238 + moduleConfig.getPrefix()
239 + "'.");
240 }
241
242 /**
243 * End plugin.
244 */
245 public void destroy() {
246 definitionFactory.destroy();
247 definitionFactory = null;
248 }
249
250 /**
251 * Create FactoryConfig and initialize it from web.xml and struts-config.xml.
252 *
253 * @param servlet ActionServlet that is managing all the modules
254 * in this web application.
255 * @param config ModuleConfig for the module with which
256 * this plugin is associated.
257 * @exception ServletException if this <code>PlugIn</code> cannot
258 * be successfully initialized.
259 */
260 protected DefinitionsFactoryConfig readFactoryConfig(
261 ActionServlet servlet,
262 ModuleConfig config)
263 throws ServletException {
264
265 // Create tiles definitions config object
266 DefinitionsFactoryConfig factoryConfig = new DefinitionsFactoryConfig();
267 // Get init parameters from web.xml files
268 try {
269 DefinitionsUtil.populateDefinitionsFactoryConfig(
270 factoryConfig,
271 servlet.getServletConfig());
272
273 } catch (Exception ex) {
274 if (log.isDebugEnabled()){
275 log.debug("", ex);
276 }
277 ex.printStackTrace();
278 throw new UnavailableException(
279 "Can't populate DefinitionsFactoryConfig class from 'web.xml': "
280 + ex.getMessage());
281 }
282
283 // Get init parameters from struts-config.xml
284 try {
285 Map strutsProperties = findStrutsPlugInConfigProperties(servlet, config);
286 factoryConfig.populate(strutsProperties);
287
288 } catch (Exception ex) {
289 if (log.isDebugEnabled()) {
290 log.debug("", ex);
291 }
292
293 throw new UnavailableException(
294 "Can't populate DefinitionsFactoryConfig class from '"
295 + config.getPrefix()
296 + "/struts-config.xml':"
297 + ex.getMessage());
298 }
299
300 return factoryConfig;
301 }
302
303 /**
304 * Find original properties set in the Struts PlugInConfig object.
305 * First, we need to find the index of this plugin. Then we retrieve the array of configs
306 * and then the object for this plugin.
307 * @param servlet ActionServlet that is managing all the modules
308 * in this web application.
309 * @param config ModuleConfig for the module with which
310 * this plug in is associated.
311 *
312 * @exception ServletException if this <code>PlugIn</code> cannot
313 * be successfully initialized.
314 */
315 protected Map findStrutsPlugInConfigProperties(
316 ActionServlet servlet,
317 ModuleConfig config)
318 throws ServletException {
319
320 return currentPlugInConfigObject.getProperties();
321 }
322
323 /**
324 * Set RequestProcessor to appropriate Tiles {@link RequestProcessor}.
325 * First, check if a RequestProcessor is specified. If yes, check if it extends
326 * the appropriate {@link TilesRequestProcessor} class. If not, set processor class to
327 * TilesRequestProcessor.
328 *
329 * @param config ModuleConfig for the module with which
330 * this plugin is associated.
331 * @throws ServletException On errors.
332 */
333 protected void initRequestProcessorClass(ModuleConfig config)
334 throws ServletException {
335
336 String tilesProcessorClassname = TilesRequestProcessor.class.getName();
337 ControllerConfig ctrlConfig = config.getControllerConfig();
338 String configProcessorClassname = ctrlConfig.getProcessorClass();
339
340 // Check if specified classname exist
341 Class configProcessorClass;
342 try {
343 configProcessorClass =
344 RequestUtils.applicationClass(configProcessorClassname);
345
346 } catch (ClassNotFoundException ex) {
347 log.fatal(
348 "Can't set TilesRequestProcessor: bad class name '"
349 + configProcessorClassname
350 + "'.");
351 throw new ServletException(ex);
352 }
353
354 // Check if it is the default request processor or Tiles one.
355 // If true, replace by Tiles' one.
356 if (configProcessorClassname.equals(RequestProcessor.class.getName())
357 || configProcessorClassname.endsWith(tilesProcessorClassname)) {
358
359 ctrlConfig.setProcessorClass(tilesProcessorClassname);
360 return;
361 }
362
363 // Check if specified request processor is compatible with Tiles.
364 Class tilesProcessorClass = TilesRequestProcessor.class;
365 if (!tilesProcessorClass.isAssignableFrom(configProcessorClass)) {
366 // Not compatible
367 String msg =
368 "TilesPlugin : Specified RequestProcessor not compatible with TilesRequestProcessor";
369 if (log.isFatalEnabled()) {
370 log.fatal(msg);
371 }
372 throw new ServletException(msg);
373 }
374 }
375
376 /**
377 * Set Tiles util implemention classname.
378 * If this property is set, the flag <code>moduleAware</code> will not be used anymore.
379 * @param tilesUtilImplClassname Classname.
380 */
381 public void setTilesUtilImplClassname(String tilesUtilImplClassname) {
382 this.tilesUtilImplClassname = tilesUtilImplClassname;
383 }
384
385 /**
386 * Get Tiles util implemention classname.
387 * @return The classname or <code>null</code> if none is set.
388 */
389 public String getTilesUtilImplClassname() {
390 return tilesUtilImplClassname;
391 }
392
393 /**
394 * Method used by the ActionServlet initializing this plugin.
395 * Set the plugin config object read from module config.
396 * @param plugInConfigObject PlugInConfig.
397 */
398 public void setCurrentPlugInConfigObject(PlugInConfig plugInConfigObject) {
399 this.currentPlugInConfigObject = plugInConfigObject;
400 }
401
402 }