/*
* Copyright 2004, 2005, 2006 Odysseus Software GmbH
*
* Licensed 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 de.odysseus.calyxo.control.impl;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.StringTokenizer;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import de.odysseus.calyxo.base.I18nSupport;
import de.odysseus.calyxo.base.ModuleContext;
import de.odysseus.calyxo.base.conf.ConfigException;
import de.odysseus.calyxo.base.misc.BaseAccessor;
import de.odysseus.calyxo.base.access.AccessSupport;
import de.odysseus.calyxo.control.Action;
import de.odysseus.calyxo.control.Command;
import de.odysseus.calyxo.control.Dispatcher;
import de.odysseus.calyxo.control.ExceptionHandler;
import de.odysseus.calyxo.control.Filter;
import de.odysseus.calyxo.control.MessageSupport;
import de.odysseus.calyxo.control.Module;
import de.odysseus.calyxo.control.Plugin;
import de.odysseus.calyxo.control.PluginContext;
import de.odysseus.calyxo.control.conf.ActionConfig;
import de.odysseus.calyxo.control.conf.ActionsConfig;
import de.odysseus.calyxo.control.conf.ControlConfig;
import de.odysseus.calyxo.control.conf.DispatchConfig;
import de.odysseus.calyxo.control.conf.ExceptionHandlerConfig;
import de.odysseus.calyxo.control.conf.ExceptionHandlersConfig;
import de.odysseus.calyxo.control.conf.FilterConfig;
import de.odysseus.calyxo.control.conf.PluginConfig;
import de.odysseus.calyxo.control.conf.PluginsConfig;
import de.odysseus.calyxo.control.conf.impl.ControlConfigParser;
import de.odysseus.calyxo.control.misc.ControlAccessor;
import de.odysseus.calyxo.control.misc.SimpleExceptionHandler;
/**
* Module class.
*
* @author Christoph Beck
*/
public class DefaultModule implements Module {
private static final Log log = LogFactory.getLog(DefaultModule.class);
/**
* Wrapper for action config and filtered command
*/
private static final class ActionCommand implements Command {
final ActionConfig config;
final Command command;
ActionCommand(ActionConfig config, Command command) {
this.config = config;
this.command = command;
}
ActionConfig getActionConfig() {
return config;
}
public DispatchConfig execute(HttpServletRequest request,
HttpServletResponse response) throws Exception {
return command.execute(request, response);
}
}
/**
* Chain of filters, that prepend an action
*/
private static final class FilteredCommand implements Command {
private final FilterConfig config;
private final Filter first;
private final Command next;
FilteredCommand(FilterConfig config, Filter first, Command next) {
this.config = config;
this.first = first;
this.next = next;
}
public DispatchConfig execute(
HttpServletRequest request,
HttpServletResponse response) throws Exception {
if (config.when(request)) {
return first.filter(request, response, next);
} else {
return next.execute(request, response);
}
}
}
private ControlConfig config;
private final ArrayList plugins = new ArrayList();
private ModuleContext context;
private PluginContext pluginContext;
// action commands by path
private final HashMap commands = new HashMap();
// exception handlers by id
private final HashMap handlers = new HashMap();
/**
* Constructor.
*
* @param context
* @throws ServletException
*/
public void init(ModuleContext context) throws ServletException {
this.context = context;
this.pluginContext = new PluginContext(context, DefaultAction.class, new DefaultDispatcher(context));
try {
initI18n();
} catch (Exception e) {
throw new ServletException("Could not initialize i18n", e);
}
try {
initMessages();
} catch (Exception e) {
throw new ServletException("Could not initialize messages", e);
}
initAccess();
try {
initConfig();
} catch (Exception e) {
throw new ServletException("Could not initialize configuration", e);
}
try {
initPlugins();
} catch (Exception e) {
throw new ServletException("Could not initialize plugins", e);
}
try {
initActions();
} catch (Exception e) {
throw new ServletException("Could not initialize actions", e);
}
try {
initExceptionHandlers();
} catch (Exception e) {
throw new ServletException("Could not initialize exceptions", e);
}
}
/**
* Destroy module.
*/
public void destroy() {
destroyPlugins();
destroyConfig();
destroyAccess();
destroyMessages();
destroyI18n();
}
/**
* Initialize plugins.
* @throws ServletException
*/
protected void initPlugins() throws Exception {
log.debug("Initializing plugins");
PluginsConfig pluginsConfig = config.getPluginsConfig();
if (pluginsConfig != null) {
Iterator pluginConfigs = pluginsConfig.getPluginConfigs();
while (pluginConfigs.hasNext()) {
PluginConfig pluginConfig = (PluginConfig)pluginConfigs.next();
log.debug("Initializing plugin " + pluginConfig.getClassName());
Class c = context.getClassLoader().loadClass(pluginConfig.getClassName());
Plugin plugin = (Plugin)c.newInstance();
plugin.init(pluginConfig, pluginContext);
plugins.add(plugin);
}
}
}
private void addHandler(ExceptionHandlerConfig config) throws Exception {
Class clazz = null;
if (config.getClassName() != null) {
clazz = context.getClassLoader().loadClass(config.getClassName());
} else {
clazz = SimpleExceptionHandler.class;
}
ExceptionHandler handler = (ExceptionHandler)clazz.newInstance();
handler.init(config, context);
handlers.put(config.getId(), handler);
}
private ExceptionHandler getHandler(ExceptionHandlerConfig config) {
return (ExceptionHandler)handlers.get(config.getId());
}
/**
* Initialize global exception handlers.
* @throws ServletException
*/
protected void initExceptionHandlers() throws Exception {
log.debug("Initializing exception handlers");
ExceptionHandlersConfig exceptionsConfig = config.getExceptionsConfig();
if (exceptionsConfig != null) {
Iterator exceptionConfigs = exceptionsConfig.getExceptionHandlerConfigs();
while (exceptionConfigs.hasNext()) {
addHandler((ExceptionHandlerConfig)exceptionConfigs.next());
}
}
ActionsConfig actionsConfig = this.config.getActionsConfig();
if (actionsConfig != null) {
Iterator configs = actionsConfig.getActionConfigs();
while (configs.hasNext()) {
ActionConfig actionConfig = (ActionConfig)configs.next();
Iterator exceptionConfigs = actionConfig.getExceptionHandlerConfigs();
while (exceptionConfigs.hasNext()) {
addHandler((ExceptionHandlerConfig)exceptionConfigs.next());
}
}
}
}
/**
* Destroy plugins.
*/
protected void destroyPlugins() {
log.debug("Destroying plugins");
Iterator plugins = this.plugins.iterator();
while (plugins.hasNext()) {
Plugin plugin = (Plugin)plugins.next();
plugin.destroy();
}
}
/**
* Initialize access
*/
protected void initAccess() {
log.debug("Initializing access support");
AccessSupport support = new AccessSupport();
// access base via ${calyxo.base}
support.put("base", new BaseAccessor(context));
// access control via ${calyxo.control}
support.put("control", new ControlAccessor(context));
context.setAttribute(AccessSupport.ACCESS_SUPPORT_KEY, support);
}
/**
* Destroy access.
*/
protected void destroyAccess() {
log.debug("Destroying access support");
context.removeAttribute(AccessSupport.ACCESS_SUPPORT_KEY);
}
/**
* Initialize i18n
*/
protected void initI18n() throws Exception {
log.debug("Initializing i18n support");
String type = context.getInitParameter("i18n-support");
I18nSupport i18n = null;
if (type == null) {
i18n = new DefaultI18nSupport();
} else {
i18n = (I18nSupport)context.getClassLoader().loadClass(type).newInstance();
}
context.setAttribute(I18nSupport.I18N_SUPPORT_KEY, i18n);
}
/**
* Destroy i18n.
*/
protected void destroyI18n() {
log.debug("Destroying i18n support");
context.removeAttribute(I18nSupport.I18N_SUPPORT_KEY);
}
/**
* Initialize message
*/
protected void initMessages() throws Exception {
log.debug("Initializing message support");
String type = context.getInitParameter("message-support");
MessageSupport messages = null;
if (type == null) {
messages = new DefaultMessageSupport();
} else {
messages = (MessageSupport)context.getClassLoader().loadClass(type).newInstance();
}
context.setAttribute(MessageSupport.MESSAGE_SUPPORT_KEY, messages);
}
/**
* Destroy message.
*/
protected void destroyMessages() {
log.debug("Destroying message support");
context.removeAttribute(MessageSupport.MESSAGE_SUPPORT_KEY);
}
/**
* Parse control configuration
*/
protected void initConfig() throws Exception {
log.debug("Initializing control configuration");
String config = context.getInitParameter("config");
ArrayList list = new ArrayList();
// add config-files
StringTokenizer tokens = new StringTokenizer(config, ",");
while (tokens.hasMoreTokens()) {
String token = tokens.nextToken().trim();
URL url = null;
url = context.getServletContext().getResource(token);
if (url == null) {
throw new ConfigException("Could not find resource " + token);
} else {
list.add(url);
}
}
URL[] inputs = (URL[])list.toArray(new URL[list.size()]);
ControlConfigParser parser =
new ControlConfigParser(context);
this.config = parser.parse(inputs);
context.setAttribute(ACTIONS_CONFIG_KEY, this.config.getActionsConfig());
}
/**
* Destroy control.
*/
protected void destroyConfig() {
log.debug("Destroying control configuration");
context.removeAttribute(ACTIONS_CONFIG_KEY);
}
/**
* Instantiate/initialize actions.
*/
protected void initActions() throws Exception {
// Instantiate and initialized action chains
ActionsConfig actionsConfig = this.config.getActionsConfig();
if (actionsConfig != null) {
Iterator configs = actionsConfig.getActionConfigs();
while (configs.hasNext()) {
ActionConfig actionConfig = (ActionConfig)configs.next();
String className = actionConfig.getClassName();
Class clazz = null;
if (className == null) {
clazz = pluginContext.getDefaultActionClass();
} else {
clazz = context.getClassLoader().loadClass(className);
}
Action action = (Action)clazz.newInstance();
action.init(actionConfig, context);
Command command = chain(actionConfig.getFilterConfigs(), action);
ActionCommand item = new ActionCommand(actionConfig, command);
commands.put(actionConfig.getPath(), item);
}
}
}
/**
* Get the context
*/
public ModuleContext getContext() {
return context;
}
/**
* Build filter chain.
* @param configs filter configs
* @param last action to be filtered
* @return linked action chain
* @throws Exception
*/
private Command chain(Iterator configs, Action last) throws Exception {
if (configs.hasNext()) {
FilterConfig config = (FilterConfig)configs.next();
Class clazz = null;
if (config.getName() != null) {
clazz = pluginContext.getFilterClass(config.getName());
if (clazz == null) {
throw new ConfigException("Unknown filter name in '" + config.toInlineString() + "'");
}
} else {
clazz = context.getClassLoader().loadClass(config.getClassName());
}
Filter filter = (Filter)clazz.newInstance();
filter.init(config, context);
return new FilteredCommand(config, filter, chain(configs, last));
} else {
return last;
}
}
/**
* Handle exception.
*/
protected DispatchConfig handle(
HttpServletRequest request,
HttpServletResponse response,
ActionConfig action,
Exception exception) throws IOException, ServletException {
ExceptionHandlerConfig config = action.findExceptionHandlerConfig(exception);
if (config == null) {
if (exception instanceof IOException) {
throw (IOException)exception;
} else if (exception instanceof ServletException) {
throw (ServletException)exception;
} else {
throw new ServletException(exception);
}
}
return getHandler(config).handle(request, response, action, exception);
}
/**
* Lookup action command
*/
private ActionCommand findActionCommand(String path) {
ActionCommand command = (ActionCommand)commands.get(path);
// while (command == null && path.length() > 1) { // try prefix patterns
// command = (ActionCommand)commands.get(path + "/*");
// path = path.substring(0, path.lastIndexOf('/'));
// }
if (command == null) {
command = (ActionCommand)commands.get("/*");
}
return command;
}
/**
* Process request.
* <p/>
* Invoke action and dispatch according to the result.
* @param request the request we are processing
* @param response the response we are creating
* @param path module path used to lookup action configuration
* @throws ServletException if action lookup fails or if action instantiation
* fails. Also exceptions passed through by the invoker are caught, wrapped
* and thrown.
* @throws IOException passed through from dispatcher
*/
public void process(
HttpServletRequest request,
HttpServletResponse response,
String path
) throws ServletException, IOException {
if (log.isTraceEnabled()) {
log.trace("Module '" + context.getName() + "': process " + path + " begin");
}
request.setAttribute(MODULE_PATH_KEY, path);
// lookup action command
ActionCommand command = findActionCommand(path);
if (command == null) {
throw new ServletException("Unknown action path '" + path + "' in module '" + context.getName() + "'");
}
request.setAttribute(ACTION_PATH_KEY, command.getActionConfig().getPath());
// execute action command
DispatchConfig result = null;
try {
result = command.execute(request, response);
} catch (Exception e) {
result = handle(request, response, command.getActionConfig(), e);
}
// dispatch request
if (result != null) {
Dispatcher dispatcher = pluginContext.getDefaultDispatcher();
String name = result.getDispatcher();
if (name == null) {
name = command.getActionConfig().getDispatcher();
}
if (name != null) {
dispatcher = pluginContext.getDispatcher(name);
if (dispatcher == null) {
throw new ServletException("Unknown dispatcher '" + name + "' for action '" + config.toInlineString() + "'");
}
}
dispatcher.dispatch(request, response, result);
}
}
}