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 }