001: /*
002: * Copyright 2004, 2005, 2006 Odysseus Software GmbH
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package de.odysseus.calyxo.control.impl;
017:
018: import java.io.IOException;
019: import java.net.URL;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.StringTokenizer;
024:
025: import javax.servlet.ServletException;
026: import javax.servlet.http.HttpServletRequest;
027: import javax.servlet.http.HttpServletResponse;
028:
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031:
032: import de.odysseus.calyxo.base.I18nSupport;
033: import de.odysseus.calyxo.base.ModuleContext;
034: import de.odysseus.calyxo.base.conf.ConfigException;
035: import de.odysseus.calyxo.base.misc.BaseAccessor;
036: import de.odysseus.calyxo.base.access.AccessSupport;
037: import de.odysseus.calyxo.control.Action;
038: import de.odysseus.calyxo.control.Command;
039: import de.odysseus.calyxo.control.Dispatcher;
040: import de.odysseus.calyxo.control.ExceptionHandler;
041: import de.odysseus.calyxo.control.Filter;
042: import de.odysseus.calyxo.control.MessageSupport;
043: import de.odysseus.calyxo.control.Module;
044: import de.odysseus.calyxo.control.Plugin;
045: import de.odysseus.calyxo.control.PluginContext;
046: import de.odysseus.calyxo.control.conf.ActionConfig;
047: import de.odysseus.calyxo.control.conf.ActionsConfig;
048: import de.odysseus.calyxo.control.conf.ControlConfig;
049: import de.odysseus.calyxo.control.conf.DispatchConfig;
050: import de.odysseus.calyxo.control.conf.ExceptionHandlerConfig;
051: import de.odysseus.calyxo.control.conf.ExceptionHandlersConfig;
052: import de.odysseus.calyxo.control.conf.FilterConfig;
053: import de.odysseus.calyxo.control.conf.PluginConfig;
054: import de.odysseus.calyxo.control.conf.PluginsConfig;
055: import de.odysseus.calyxo.control.conf.impl.ControlConfigParser;
056: import de.odysseus.calyxo.control.misc.ControlAccessor;
057: import de.odysseus.calyxo.control.misc.SimpleExceptionHandler;
058:
059: /**
060: * Module class.
061: *
062: * @author Christoph Beck
063: */
064: public class DefaultModule implements Module {
065: private static final Log log = LogFactory
066: .getLog(DefaultModule.class);
067:
068: /**
069: * Wrapper for action config and filtered command
070: */
071: private static final class ActionCommand implements Command {
072: final ActionConfig config;
073: final Command command;
074:
075: ActionCommand(ActionConfig config, Command command) {
076: this .config = config;
077: this .command = command;
078: }
079:
080: ActionConfig getActionConfig() {
081: return config;
082: }
083:
084: public DispatchConfig execute(HttpServletRequest request,
085: HttpServletResponse response) throws Exception {
086: return command.execute(request, response);
087: }
088: }
089:
090: /**
091: * Chain of filters, that prepend an action
092: */
093: private static final class FilteredCommand implements Command {
094: private final FilterConfig config;
095: private final Filter first;
096: private final Command next;
097:
098: FilteredCommand(FilterConfig config, Filter first, Command next) {
099: this .config = config;
100: this .first = first;
101: this .next = next;
102: }
103:
104: public DispatchConfig execute(HttpServletRequest request,
105: HttpServletResponse response) throws Exception {
106: if (config.when(request)) {
107: return first.filter(request, response, next);
108: } else {
109: return next.execute(request, response);
110: }
111: }
112: }
113:
114: private ControlConfig config;
115: private final ArrayList plugins = new ArrayList();
116: private ModuleContext context;
117: private PluginContext pluginContext;
118: // action commands by path
119: private final HashMap commands = new HashMap();
120: // exception handlers by id
121: private final HashMap handlers = new HashMap();
122:
123: /**
124: * Constructor.
125: *
126: * @param context
127: * @throws ServletException
128: */
129: public void init(ModuleContext context) throws ServletException {
130: this .context = context;
131: this .pluginContext = new PluginContext(context,
132: DefaultAction.class, new DefaultDispatcher(context));
133:
134: try {
135: initI18n();
136: } catch (Exception e) {
137: throw new ServletException("Could not initialize i18n", e);
138: }
139: try {
140: initMessages();
141: } catch (Exception e) {
142: throw new ServletException("Could not initialize messages",
143: e);
144: }
145: initAccess();
146: try {
147: initConfig();
148: } catch (Exception e) {
149: throw new ServletException(
150: "Could not initialize configuration", e);
151: }
152: try {
153: initPlugins();
154: } catch (Exception e) {
155: throw new ServletException("Could not initialize plugins",
156: e);
157: }
158: try {
159: initActions();
160: } catch (Exception e) {
161: throw new ServletException("Could not initialize actions",
162: e);
163: }
164: try {
165: initExceptionHandlers();
166: } catch (Exception e) {
167: throw new ServletException(
168: "Could not initialize exceptions", e);
169: }
170: }
171:
172: /**
173: * Destroy module.
174: */
175: public void destroy() {
176: destroyPlugins();
177: destroyConfig();
178: destroyAccess();
179: destroyMessages();
180: destroyI18n();
181: }
182:
183: /**
184: * Initialize plugins.
185: * @throws ServletException
186: */
187: protected void initPlugins() throws Exception {
188: log.debug("Initializing plugins");
189: PluginsConfig pluginsConfig = config.getPluginsConfig();
190: if (pluginsConfig != null) {
191: Iterator pluginConfigs = pluginsConfig.getPluginConfigs();
192: while (pluginConfigs.hasNext()) {
193: PluginConfig pluginConfig = (PluginConfig) pluginConfigs
194: .next();
195: log.debug("Initializing plugin "
196: + pluginConfig.getClassName());
197: Class c = context.getClassLoader().loadClass(
198: pluginConfig.getClassName());
199: Plugin plugin = (Plugin) c.newInstance();
200: plugin.init(pluginConfig, pluginContext);
201: plugins.add(plugin);
202: }
203: }
204: }
205:
206: private void addHandler(ExceptionHandlerConfig config)
207: throws Exception {
208: Class clazz = null;
209: if (config.getClassName() != null) {
210: clazz = context.getClassLoader().loadClass(
211: config.getClassName());
212: } else {
213: clazz = SimpleExceptionHandler.class;
214: }
215: ExceptionHandler handler = (ExceptionHandler) clazz
216: .newInstance();
217: handler.init(config, context);
218: handlers.put(config.getId(), handler);
219: }
220:
221: private ExceptionHandler getHandler(ExceptionHandlerConfig config) {
222: return (ExceptionHandler) handlers.get(config.getId());
223: }
224:
225: /**
226: * Initialize global exception handlers.
227: * @throws ServletException
228: */
229: protected void initExceptionHandlers() throws Exception {
230: log.debug("Initializing exception handlers");
231: ExceptionHandlersConfig exceptionsConfig = config
232: .getExceptionsConfig();
233: if (exceptionsConfig != null) {
234: Iterator exceptionConfigs = exceptionsConfig
235: .getExceptionHandlerConfigs();
236: while (exceptionConfigs.hasNext()) {
237: addHandler((ExceptionHandlerConfig) exceptionConfigs
238: .next());
239: }
240: }
241: ActionsConfig actionsConfig = this .config.getActionsConfig();
242: if (actionsConfig != null) {
243: Iterator configs = actionsConfig.getActionConfigs();
244: while (configs.hasNext()) {
245: ActionConfig actionConfig = (ActionConfig) configs
246: .next();
247: Iterator exceptionConfigs = actionConfig
248: .getExceptionHandlerConfigs();
249: while (exceptionConfigs.hasNext()) {
250: addHandler((ExceptionHandlerConfig) exceptionConfigs
251: .next());
252: }
253: }
254: }
255: }
256:
257: /**
258: * Destroy plugins.
259: */
260: protected void destroyPlugins() {
261: log.debug("Destroying plugins");
262: Iterator plugins = this .plugins.iterator();
263: while (plugins.hasNext()) {
264: Plugin plugin = (Plugin) plugins.next();
265: plugin.destroy();
266: }
267: }
268:
269: /**
270: * Initialize access
271: */
272: protected void initAccess() {
273: log.debug("Initializing access support");
274: AccessSupport support = new AccessSupport();
275: // access base via ${calyxo.base}
276: support.put("base", new BaseAccessor(context));
277: // access control via ${calyxo.control}
278: support.put("control", new ControlAccessor(context));
279: context.setAttribute(AccessSupport.ACCESS_SUPPORT_KEY, support);
280: }
281:
282: /**
283: * Destroy access.
284: */
285: protected void destroyAccess() {
286: log.debug("Destroying access support");
287: context.removeAttribute(AccessSupport.ACCESS_SUPPORT_KEY);
288: }
289:
290: /**
291: * Initialize i18n
292: */
293: protected void initI18n() throws Exception {
294: log.debug("Initializing i18n support");
295: String type = context.getInitParameter("i18n-support");
296: I18nSupport i18n = null;
297: if (type == null) {
298: i18n = new DefaultI18nSupport();
299: } else {
300: i18n = (I18nSupport) context.getClassLoader().loadClass(
301: type).newInstance();
302: }
303: context.setAttribute(I18nSupport.I18N_SUPPORT_KEY, i18n);
304: }
305:
306: /**
307: * Destroy i18n.
308: */
309: protected void destroyI18n() {
310: log.debug("Destroying i18n support");
311: context.removeAttribute(I18nSupport.I18N_SUPPORT_KEY);
312: }
313:
314: /**
315: * Initialize message
316: */
317: protected void initMessages() throws Exception {
318: log.debug("Initializing message support");
319: String type = context.getInitParameter("message-support");
320: MessageSupport messages = null;
321: if (type == null) {
322: messages = new DefaultMessageSupport();
323: } else {
324: messages = (MessageSupport) context.getClassLoader()
325: .loadClass(type).newInstance();
326: }
327: context.setAttribute(MessageSupport.MESSAGE_SUPPORT_KEY,
328: messages);
329: }
330:
331: /**
332: * Destroy message.
333: */
334: protected void destroyMessages() {
335: log.debug("Destroying message support");
336: context.removeAttribute(MessageSupport.MESSAGE_SUPPORT_KEY);
337: }
338:
339: /**
340: * Parse control configuration
341: */
342: protected void initConfig() throws Exception {
343: log.debug("Initializing control configuration");
344: String config = context.getInitParameter("config");
345: ArrayList list = new ArrayList();
346: // add config-files
347: StringTokenizer tokens = new StringTokenizer(config, ",");
348: while (tokens.hasMoreTokens()) {
349: String token = tokens.nextToken().trim();
350: URL url = null;
351: url = context.getServletContext().getResource(token);
352: if (url == null) {
353: throw new ConfigException("Could not find resource "
354: + token);
355: } else {
356: list.add(url);
357: }
358: }
359: URL[] inputs = (URL[]) list.toArray(new URL[list.size()]);
360: ControlConfigParser parser = new ControlConfigParser(context);
361: this .config = parser.parse(inputs);
362: context.setAttribute(ACTIONS_CONFIG_KEY, this .config
363: .getActionsConfig());
364: }
365:
366: /**
367: * Destroy control.
368: */
369: protected void destroyConfig() {
370: log.debug("Destroying control configuration");
371: context.removeAttribute(ACTIONS_CONFIG_KEY);
372: }
373:
374: /**
375: * Instantiate/initialize actions.
376: */
377: protected void initActions() throws Exception {
378: // Instantiate and initialized action chains
379: ActionsConfig actionsConfig = this .config.getActionsConfig();
380: if (actionsConfig != null) {
381: Iterator configs = actionsConfig.getActionConfigs();
382: while (configs.hasNext()) {
383: ActionConfig actionConfig = (ActionConfig) configs
384: .next();
385: String className = actionConfig.getClassName();
386: Class clazz = null;
387: if (className == null) {
388: clazz = pluginContext.getDefaultActionClass();
389: } else {
390: clazz = context.getClassLoader().loadClass(
391: className);
392: }
393: Action action = (Action) clazz.newInstance();
394: action.init(actionConfig, context);
395: Command command = chain(
396: actionConfig.getFilterConfigs(), action);
397: ActionCommand item = new ActionCommand(actionConfig,
398: command);
399: commands.put(actionConfig.getPath(), item);
400: }
401: }
402: }
403:
404: /**
405: * Get the context
406: */
407: public ModuleContext getContext() {
408: return context;
409: }
410:
411: /**
412: * Build filter chain.
413: * @param configs filter configs
414: * @param last action to be filtered
415: * @return linked action chain
416: * @throws Exception
417: */
418: private Command chain(Iterator configs, Action last)
419: throws Exception {
420: if (configs.hasNext()) {
421: FilterConfig config = (FilterConfig) configs.next();
422: Class clazz = null;
423: if (config.getName() != null) {
424: clazz = pluginContext.getFilterClass(config.getName());
425: if (clazz == null) {
426: throw new ConfigException(
427: "Unknown filter name in '"
428: + config.toInlineString() + "'");
429: }
430: } else {
431: clazz = context.getClassLoader().loadClass(
432: config.getClassName());
433: }
434: Filter filter = (Filter) clazz.newInstance();
435: filter.init(config, context);
436: return new FilteredCommand(config, filter, chain(configs,
437: last));
438: } else {
439: return last;
440: }
441: }
442:
443: /**
444: * Handle exception.
445: */
446: protected DispatchConfig handle(HttpServletRequest request,
447: HttpServletResponse response, ActionConfig action,
448: Exception exception) throws IOException, ServletException {
449:
450: ExceptionHandlerConfig config = action
451: .findExceptionHandlerConfig(exception);
452: if (config == null) {
453: if (exception instanceof IOException) {
454: throw (IOException) exception;
455: } else if (exception instanceof ServletException) {
456: throw (ServletException) exception;
457: } else {
458: throw new ServletException(exception);
459: }
460: }
461: return getHandler(config).handle(request, response, action,
462: exception);
463: }
464:
465: /**
466: * Lookup action command
467: */
468: private ActionCommand findActionCommand(String path) {
469: ActionCommand command = (ActionCommand) commands.get(path);
470: // while (command == null && path.length() > 1) { // try prefix patterns
471: // command = (ActionCommand)commands.get(path + "/*");
472: // path = path.substring(0, path.lastIndexOf('/'));
473: // }
474: if (command == null) {
475: command = (ActionCommand) commands.get("/*");
476: }
477: return command;
478: }
479:
480: /**
481: * Process request.
482: * <p/>
483: * Invoke action and dispatch according to the result.
484: * @param request the request we are processing
485: * @param response the response we are creating
486: * @param path module path used to lookup action configuration
487: * @throws ServletException if action lookup fails or if action instantiation
488: * fails. Also exceptions passed through by the invoker are caught, wrapped
489: * and thrown.
490: * @throws IOException passed through from dispatcher
491: */
492: public void process(HttpServletRequest request,
493: HttpServletResponse response, String path)
494: throws ServletException, IOException {
495: if (log.isTraceEnabled()) {
496: log.trace("Module '" + context.getName() + "': process "
497: + path + " begin");
498: }
499:
500: request.setAttribute(MODULE_PATH_KEY, path);
501:
502: // lookup action command
503: ActionCommand command = findActionCommand(path);
504: if (command == null) {
505: throw new ServletException("Unknown action path '" + path
506: + "' in module '" + context.getName() + "'");
507: }
508:
509: request.setAttribute(ACTION_PATH_KEY, command.getActionConfig()
510: .getPath());
511:
512: // execute action command
513: DispatchConfig result = null;
514: try {
515: result = command.execute(request, response);
516: } catch (Exception e) {
517: result = handle(request, response, command
518: .getActionConfig(), e);
519: }
520:
521: // dispatch request
522: if (result != null) {
523: Dispatcher dispatcher = pluginContext
524: .getDefaultDispatcher();
525: String name = result.getDispatcher();
526: if (name == null) {
527: name = command.getActionConfig().getDispatcher();
528: }
529: if (name != null) {
530: dispatcher = pluginContext.getDispatcher(name);
531: if (dispatcher == null) {
532: throw new ServletException("Unknown dispatcher '"
533: + name + "' for action '"
534: + config.toInlineString() + "'");
535: }
536: }
537: dispatcher.dispatch(request, response, result);
538: }
539: }
540: }
|