View Javadoc

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