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.tiles2;
23  
24  import jakarta.servlet.ServletException;
25  
26  import org.apache.struts.action.ActionServlet;
27  import org.apache.struts.action.PlugIn;
28  import org.apache.struts.action.RequestProcessor;
29  import org.apache.struts.chain.ComposableRequestProcessor;
30  import org.apache.struts.config.ControllerConfig;
31  import org.apache.struts.config.ModuleConfig;
32  import org.apache.struts.config.PlugInConfig;
33  import org.apache.struts.tiles2.util.PlugInConfigContextAdapter;
34  import org.apache.struts.util.RequestUtils;
35  import org.apache.tiles.TilesContainer;
36  import org.apache.tiles.TilesException;
37  import org.apache.tiles.access.TilesAccess;
38  import org.apache.tiles.definition.DefinitionsFactory;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  import org.slf4j.Marker;
42  import org.slf4j.MarkerFactory;
43  
44  /**
45   * Tiles Plugin used to initialize Tiles.
46   * This plugin is to be used with Struts 1.1 in association with
47   * {@link TilesRequestProcessor}.
48   * <br>
49   * This plugin creates one definition factory for each Struts-module. The definition factory
50   * configuration is read first from 'web.xml' (backward compatibility), then it is
51   * overloaded with values found in the plugin property values.
52   * <br>
53   * The plugin changes the Struts configuration by specifying a {@link TilesRequestProcessor} as
54   * request processor. If you want to use your own RequestProcessor,
55   * it should subclass TilesRequestProcessor.
56   * <br>
57   * This plugin can also be used to create one single factory for all modules.
58   * This behavior is enabled by specifying <code>moduleAware=false</code> in each
59   * plugin properties. In this case, the definition factory
60   * configuration file is read by the first Tiles plugin to be initialized. The order is
61   * determined by the order of modules declaration in web.xml. The first module
62   * is always the default one if it exists.
63   * The plugin should be declared in each struts-config.xml file in order to
64   * properly initialize the request processor.
65   *
66   * @version $Rev$ $Date$
67   * @since Struts 1.1
68   */
69  // TODO Complete the plugin to be module-aware.
70  public class TilesPlugin implements PlugIn {
71  
72      /**
73       * Marker for logging of fatal errors.
74       */
75      private final static Marker FATAL = MarkerFactory.getMarker("FATAL");
76  
77      /**
78       * The {@code Log} instance for this class.
79       */
80      private final Logger log =
81          LoggerFactory.getLogger(TilesPlugin.class);
82  
83      /**
84       * Is the factory module aware?
85       */
86      protected boolean moduleAware = false;
87  
88      /**
89       * The plugin config object provided by the ActionServlet initializing
90       * this plugin.
91       */
92      protected PlugInConfig currentPlugInConfigObject = null;
93  
94      /**
95       * The plugin config object adapted to become a context-like object, that
96       * exposes init parameters methods.
97       */
98      protected PlugInConfigContextAdapter currentPlugInConfigContextAdapter = null;
99  
100     /**
101      * Get the module aware flag.
102      * @return <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 boolean isModuleAware() {
106         return moduleAware;
107     }
108 
109     /**
110      * Set the module aware flag.
111      * This flag is only meaningful if the property <code>tilesUtilImplClassname</code> is not
112      * set.
113      * @param moduleAware <code>true</code>: user wants a single factory instance,
114      * <code>false:</code> user wants multiple factory instances (one per module with Struts)
115      */
116     public void setModuleAware(boolean moduleAware) {
117         this.moduleAware = moduleAware;
118     }
119 
120     /**
121      * <p>Receive notification that the specified module is being
122      * started up.</p>
123      *
124      * @param servlet ActionServlet that is managing all the modules
125      *  in this web application.
126      * @param moduleConfig ModuleConfig for the module with which
127      *  this plugin is associated.
128      *
129      * @exception ServletException if this <code>PlugIn</code> cannot
130      *  be successfully initialized.
131      */
132     public void init(ActionServlet servlet, ModuleConfig moduleConfig)
133         throws ServletException {
134 
135         currentPlugInConfigContextAdapter = new PlugInConfigContextAdapter(
136                 this.currentPlugInConfigObject, servlet.getServletContext());
137 
138         // Set RequestProcessor class
139         this.initRequestProcessorClass(moduleConfig);
140 
141         // Initialize Tiles
142         try {
143             TilesPluginContainerFactory factory;
144             TilesContainer container;
145             if (moduleAware) {
146                 factory = new TilesPluginContainerFactory();
147                 container = TilesAccess
148                         .getContainer(currentPlugInConfigContextAdapter);
149                 if (container == null) {
150                     container = factory.createContainer(
151                             currentPlugInConfigContextAdapter);
152                     TilesAccess.setContainer(currentPlugInConfigContextAdapter,
153                             container);
154                 }
155                 if (container instanceof TilesPluginContainer) {
156                     TilesPluginContainer pluginContainer =
157                         (TilesPluginContainer) container;
158                     // If we have a definition factory for the current module
159                     // prefix then we are trying to re-initialize the same module,
160                     // and it is wrong!
161                     if (pluginContainer.getProperDefinitionsFactory(moduleConfig
162                             .getPrefix()) != null) {
163                         throw new ServletException("Tiles definitions factory for module '"
164                                         + moduleConfig.getPrefix()
165                                         + "' has already been configured");
166                     }
167 
168                     DefinitionsFactory defsFactory = factory
169                             .createDefinitionsFactory(pluginContainer, currentPlugInConfigContextAdapter);
170                     pluginContainer.setDefinitionsFactory(moduleConfig.getPrefix(), defsFactory);
171                 } else {
172                     log.warn("The created container is not instance of "
173                             + "TilesPluginContainer"
174                             + " and cannot be configured correctly");
175                 }
176             } else {
177                 if (currentPlugInConfigContextAdapter.getApplicationScope()
178                         .containsKey(TilesAccess.CONTAINER_ATTRIBUTE)) {
179                     throw new ServletException(
180                             "Tiles container has already been configured");
181                 }
182                 factory = new TilesPluginContainerFactory();
183                 container = factory.createContainer(
184                         currentPlugInConfigContextAdapter);
185                 TilesAccess.setContainer(currentPlugInConfigContextAdapter,
186                         container);
187             }
188         } catch (TilesException e) {
189             log.error(FATAL, "Unable to retrieve tiles factory.", e);
190             throw new IllegalStateException("Unable to instantiate container.");
191         }
192     }
193 
194     /**
195      * End plugin.
196      */
197     public void destroy() {
198         try {
199             TilesAccess.setContainer(currentPlugInConfigContextAdapter, null);
200         } catch (TilesException e) {
201             log.warn("Unable to remove tiles container from service.");
202         }
203     }
204 
205     /**
206      * Set RequestProcessor to appropriate Tiles {@link RequestProcessor}.
207      * First, check if a RequestProcessor is specified. If yes, check if it extends
208      * the appropriate {@link TilesRequestProcessor} class. If not, set processor class to
209      * TilesRequestProcessor.
210      *
211      * @param config ModuleConfig for the module with which
212      *  this plugin is associated.
213      * @throws ServletException On errors.
214      */
215     protected void initRequestProcessorClass(ModuleConfig config)
216         throws ServletException {
217 
218         String tilesProcessorClassname = TilesRequestProcessor.class.getName();
219         ControllerConfig ctrlConfig = config.getControllerConfig();
220         String configProcessorClassname = ctrlConfig.getProcessorClass();
221 
222         // Check if specified classname exist
223         Class<?> configProcessorClass;
224         try {
225             configProcessorClass =
226                 RequestUtils.applicationClass(configProcessorClassname);
227 
228         } catch (ClassNotFoundException ex) {
229             log.error(FATAL, "Can't set TilesRequestProcessor: bad class name '{}'.",
230                 configProcessorClassname);
231             throw new ServletException(ex);
232         }
233 
234         // Check to see if request processor uses struts-chain.  If so,
235         // no need to replace the request processor.
236         if (ComposableRequestProcessor.class.isAssignableFrom(configProcessorClass)) {
237             return;
238         }
239 
240         // Check if it is the default request processor or Tiles one.
241         // If true, replace by Tiles' one.
242         if (configProcessorClassname.equals(RequestProcessor.class.getName())
243             || configProcessorClassname.endsWith(tilesProcessorClassname)) {
244 
245             ctrlConfig.setProcessorClass(tilesProcessorClassname);
246             return;
247         }
248 
249         // Check if specified request processor is compatible with Tiles.
250         Class<TilesRequestProcessor> tilesProcessorClass = TilesRequestProcessor.class;
251         if (!tilesProcessorClass.isAssignableFrom(configProcessorClass)) {
252             // Not compatible
253             String msg =
254                 "TilesPlugin : Specified RequestProcessor not compatible with TilesRequestProcessor";
255             log.error(FATAL, msg);
256             throw new ServletException(msg);
257         }
258     }
259 
260     /**
261      * Method used by the ActionServlet initializing this plugin.
262      * Set the plugin config object read from module config.
263      * @param plugInConfigObject PlugInConfig.
264      */
265     public void setCurrentPlugInConfigObject(PlugInConfig plugInConfigObject) {
266         this.currentPlugInConfigObject = plugInConfigObject;
267     }
268 }