/*
* $Id: TilesPlugin.java 603355 2007-12-11 20:48:27Z apetrelli $
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.struts.tiles2;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts.action.ActionServlet;
import org.apache.struts.action.PlugIn;
import org.apache.struts.action.RequestProcessor;
import org.apache.struts.chain.ComposableRequestProcessor;
import org.apache.struts.config.ControllerConfig;
import org.apache.struts.config.ModuleConfig;
import org.apache.struts.config.PlugInConfig;
import org.apache.struts.tiles2.preparer.StrutsPreparerFactory;
import org.apache.struts.tiles2.util.PlugInConfigContextAdapter;
import org.apache.struts.util.ModuleUtils;
import org.apache.struts.util.RequestUtils;
import org.apache.tiles.TilesContainer;
import org.apache.tiles.TilesException;
import org.apache.tiles.access.TilesAccess;
import org.apache.tiles.context.ChainedTilesContextFactory;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.definition.DefinitionsFactory;
import org.apache.tiles.definition.UrlDefinitionsFactory;
import org.apache.tiles.factory.KeyedDefinitionsFactoryTilesContainerFactory;
import org.apache.tiles.factory.TilesContainerFactory;
import org.apache.tiles.impl.BasicTilesContainer;
import org.apache.tiles.impl.KeyedDefinitionsFactoryTilesContainer;
import org.apache.tiles.impl.KeyedDefinitionsFactoryTilesContainer.KeyExtractor;
import org.apache.tiles.servlet.context.ServletTilesRequestContext;
/**
* Tiles Plugin used to initialize Tiles.
* This plugin is to be used with Struts 1.1 in association with
* {@link TilesRequestProcessor}.
* <br>
* This plugin creates one definition factory for each Struts-module. The definition factory
* configuration is read first from 'web.xml' (backward compatibility), then it is
* overloaded with values found in the plugin property values.
* <br>
* The plugin changes the Struts configuration by specifying a {@link TilesRequestProcessor} as
* request processor. If you want to use your own RequestProcessor,
* it should subclass TilesRequestProcessor.
* <br>
* This plugin can also be used to create one single factory for all modules.
* This behavior is enabled by specifying <code>moduleAware=false</code> in each
* plugin properties. In this case, the definition factory
* configuration file is read by the first Tiles plugin to be initialized. The order is
* determined by the order of modules declaration in web.xml. The first module
* is always the default one if it exists.
* The plugin should be declared in each struts-config.xml file in order to
* properly initialize the request processor.
*
* @version $Rev: 603355 $ $Date: 2007-12-11 21:48:27 +0100 (Mar, 11 déc 2007) $
* @since Struts 1.1
*/
// TODO Complete the plugin to be module-aware.
public class TilesPlugin implements PlugIn {
/**
* Defaults form Tiles 2 configuration in case of a module-aware
* configuration.
*/
private static final Map MODULE_AWARE_DEFAULTS =
new HashMap();
/**
* Defaults form Tiles 2 configuration in case of a configuration without
* modules.
*/
private static final Map NO_MODULE_DEFAULTS =
new HashMap();
static {
NO_MODULE_DEFAULTS.put(TilesContainerFactory
.CONTEXT_FACTORY_INIT_PARAM,
ChainedTilesContextFactory.class.getName());
NO_MODULE_DEFAULTS.put(TilesContainerFactory
.DEFINITIONS_FACTORY_INIT_PARAM,
UrlDefinitionsFactory.class.getName());
NO_MODULE_DEFAULTS.put(TilesContainerFactory
.PREPARER_FACTORY_INIT_PARAM,
StrutsPreparerFactory.class.getName());
MODULE_AWARE_DEFAULTS.putAll(NO_MODULE_DEFAULTS);
MODULE_AWARE_DEFAULTS.put(
KeyedDefinitionsFactoryTilesContainerFactory
.KEY_EXTRACTOR_CLASS_INIT_PARAM,
ModuleKeyExtractor.class.getName());
MODULE_AWARE_DEFAULTS.put(TilesContainerFactory
.CONTAINER_FACTORY_INIT_PARAM,
KeyedDefinitionsFactoryTilesContainerFactory.class.getName());
}
/**
* Commons Logging instance.
*/
protected static Log log = LogFactory.getLog(TilesPlugin.class);
/**
* Is the factory module aware?
*/
protected boolean moduleAware = false;
/**
* The plugin config object provided by the ActionServlet initializing
* this plugin.
*/
protected PlugInConfig currentPlugInConfigObject = null;
/**
* The plugin config object adapted to become a context-like object, that
* exposes init parameters methods.
*/
protected PlugInConfigContextAdapter currentPlugInConfigContextAdapter = null;
/**
* Get the module aware flag.
* @return <code>true</code>: user wants a single factory instance,
* <code>false:</code> user wants multiple factory instances (one per module with Struts)
*/
public boolean isModuleAware() {
return moduleAware;
}
/**
* Set the module aware flag.
* This flag is only meaningful if the property <code>tilesUtilImplClassname</code> is not
* set.
* @param moduleAware <code>true</code>: user wants a single factory instance,
* <code>false:</code> user wants multiple factory instances (one per module with Struts)
*/
public void setModuleAware(boolean moduleAware) {
this.moduleAware = moduleAware;
}
/**
* <p>Receive notification that the specified module is being
* started up.</p>
*
* @param servlet ActionServlet that is managing all the modules
* in this web application.
* @param moduleConfig ModuleConfig for the module with which
* this plugin is associated.
*
* @exception ServletException if this <code>PlugIn</code> cannot
* be successfully initialized.
*/
public void init(ActionServlet servlet, ModuleConfig moduleConfig)
throws ServletException {
currentPlugInConfigContextAdapter = new PlugInConfigContextAdapter(
this.currentPlugInConfigObject, servlet.getServletContext());
// Set RequestProcessor class
this.initRequestProcessorClass(moduleConfig);
// Initialize Tiles
try {
TilesContainerFactory factory;
TilesContainer container;
if (moduleAware) {
factory = TilesContainerFactory.getFactory(
currentPlugInConfigContextAdapter,
MODULE_AWARE_DEFAULTS);
container = TilesAccess.getContainer(
currentPlugInConfigContextAdapter);
if (container == null) {
container = factory.createContainer(
currentPlugInConfigContextAdapter);
TilesAccess.setContainer(currentPlugInConfigContextAdapter,
container);
}
if (container instanceof KeyedDefinitionsFactoryTilesContainer) {
KeyedDefinitionsFactoryTilesContainer keyedContainer =
(KeyedDefinitionsFactoryTilesContainer) container;
// If we have a definition factory for the current module
// prefix then we are trying to re-initialize the same module,
// and it is wrong!
if (keyedContainer.getProperDefinitionsFactory(moduleConfig
.getPrefix()) != null) {
throw new ServletException("Tiles definitions factory for module '"
+ moduleConfig.getPrefix()
+ "' has already been configured");
}
if (factory instanceof KeyedDefinitionsFactoryTilesContainerFactory) {
DefinitionsFactory defsFactory =
((KeyedDefinitionsFactoryTilesContainerFactory) factory)
.createDefinitionsFactory(currentPlugInConfigContextAdapter);
Map initParameters = new HashMap();
String param = (String) currentPlugInConfigObject
.getProperties().get(BasicTilesContainer
.DEFINITIONS_CONFIG);
if (param == null) {
param = (String) currentPlugInConfigObject
.getProperties().get("definitions-config");
}
if (param != null) {
initParameters.put(BasicTilesContainer
.DEFINITIONS_CONFIG, param);
}
keyedContainer.setDefinitionsFactory(moduleConfig.getPrefix(),
defsFactory, initParameters);
} else {
log.warn("The created factory is not instance of "
+ "KeyedDefinitionsFactoryTilesContainerFactory"
+ " and cannot be configured correctly");
}
} else {
log.warn("The created container is not instance of "
+ "KeyedDefinitionsFactoryTilesContainer"
+ " and cannot be configured correctly");
}
} else {
factory = TilesContainerFactory
.getFactory(currentPlugInConfigContextAdapter);
if (TilesAccess.getContainer(currentPlugInConfigContextAdapter) != null) {
throw new ServletException(
"Tiles container has already been configured");
}
container = factory.createContainer(
currentPlugInConfigContextAdapter);
TilesAccess.setContainer(currentPlugInConfigContextAdapter,
container);
}
} catch (TilesException e) {
log.fatal("Unable to retrieve tiles factory.", e);
throw new IllegalStateException("Unable to instantiate container.");
}
}
/**
* End plugin.
*/
public void destroy() {
try {
TilesAccess.setContainer(currentPlugInConfigContextAdapter, null);
} catch (TilesException e) {
log.warn("Unable to remove tiles container from service.");
}
}
/**
* Set RequestProcessor to appropriate Tiles {@link RequestProcessor}.
* First, check if a RequestProcessor is specified. If yes, check if it extends
* the appropriate {@link TilesRequestProcessor} class. If not, set processor class to
* TilesRequestProcessor.
*
* @param config ModuleConfig for the module with which
* this plugin is associated.
* @throws ServletException On errors.
*/
protected void initRequestProcessorClass(ModuleConfig config)
throws ServletException {
String tilesProcessorClassname = TilesRequestProcessor.class.getName();
ControllerConfig ctrlConfig = config.getControllerConfig();
String configProcessorClassname = ctrlConfig.getProcessorClass();
// Check if specified classname exist
Class configProcessorClass;
try {
configProcessorClass =
RequestUtils.applicationClass(configProcessorClassname);
} catch (ClassNotFoundException ex) {
log.fatal(
"Can't set TilesRequestProcessor: bad class name '"
+ configProcessorClassname
+ "'.");
throw new ServletException(ex);
}
// Check to see if request processor uses struts-chain. If so,
// no need to replace the request processor.
if (ComposableRequestProcessor.class.isAssignableFrom(configProcessorClass)) {
return;
}
// Check if it is the default request processor or Tiles one.
// If true, replace by Tiles' one.
if (configProcessorClassname.equals(RequestProcessor.class.getName())
|| configProcessorClassname.endsWith(tilesProcessorClassname)) {
ctrlConfig.setProcessorClass(tilesProcessorClassname);
return;
}
// Check if specified request processor is compatible with Tiles.
Class tilesProcessorClass = TilesRequestProcessor.class;
if (!tilesProcessorClass.isAssignableFrom(configProcessorClass)) {
// Not compatible
String msg =
"TilesPlugin : Specified RequestProcessor not compatible with TilesRequestProcessor";
if (log.isFatalEnabled()) {
log.fatal(msg);
}
throw new ServletException(msg);
}
}
/**
* Method used by the ActionServlet initializing this plugin.
* Set the plugin config object read from module config.
* @param plugInConfigObject PlugInConfig.
*/
public void setCurrentPlugInConfigObject(PlugInConfig plugInConfigObject) {
this.currentPlugInConfigObject = plugInConfigObject;
}
/**
* Extracts the definitions factory key according to the module prefix.
*/
public static class ModuleKeyExtractor implements KeyExtractor {
/** {@inheritDoc} */
public String getDefinitionsFactoryKey(TilesRequestContext request) {
String retValue = null;
if (request instanceof ServletTilesRequestContext) {
HttpServletRequest servletRequest =
(HttpServletRequest) ((ServletTilesRequestContext) request).getRequest();
ModuleConfig config = ModuleUtils.getInstance().getModuleConfig(
servletRequest);
if (config == null) {
// ModuleConfig not found in current request. Select it.
ModuleUtils.getInstance().selectModule(servletRequest,
servletRequest.getSession().getServletContext());
config = ModuleUtils.getInstance().getModuleConfig(servletRequest);
}
if (config != null) {
retValue = config.getPrefix();
}
}
return retValue;
}
}
}