View Javadoc

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