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