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 }