001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010: package org.mmbase.module;
011:
012: import org.mmbase.module.core.MMBaseContext;
013: import java.util.*;
014: import java.net.*;
015: import java.lang.reflect.*;
016: import org.xml.sax.*;
017:
018: import org.mmbase.util.*;
019: import org.mmbase.util.xml.ModuleReader;
020:
021: import org.mmbase.util.functions.*;
022: import org.mmbase.util.logging.Logging;
023: import org.mmbase.util.logging.Logger;
024:
025: /**
026: * An MMBase Module is an extension of this class, which is configured by an XML file in the <mmbase
027: * config dir>/modules directory. All modules whose xml configuration file defines them as 'active' are
028: * automaticly loaded and initialized.
029: *
030: * There are several modules which are more or less compulsary in MMBase, like the 'mmbaseroot'
031: * module, the actual core of MMBase implemented by {@link org.mmbase.module.core.MMBase}, and the
032: * 'jdbc' module.
033: *
034: * @author Rico Jansen
035: * @author Rob Vermeulen (securitypart)
036: * @author Pierre van Rooden
037: *
038: * @version $Id: Module.java,v 1.96 2007/12/06 08:06:43 michiel Exp $
039: */
040: public abstract class Module extends DescribedFunctionProvider {
041:
042: /**
043: * State identifier for module startup time.
044: */
045: public final String STATE_START_TIME = "Start Time";
046:
047: private static final Logger log = Logging
048: .getLoggerInstance(Module.class);
049:
050: // A map containing all currently loaded modules by name.
051: private static Map<String, Module> modules;
052:
053: /**
054: * This function returns the Module's version number as an Integer.
055: * It takes no parameters.
056: * This function can be called through the function framework.
057: * @since MMBase-1.8
058: */
059: protected Function<Integer> getVersionFunction = new AbstractFunction<Integer>(
060: "getVersion") {
061: public Integer getFunctionValue(Parameters arguments) {
062: return getVersion();
063: }
064: };
065:
066: /**
067: * This function returns the Module's maintainer as a String.
068: * It takes no parameters.
069: * This function can be called through the function framework.
070: * @since MMBase-1.8
071: */
072: protected Function<String> getMaintainerFunction = new AbstractFunction<String>(
073: "getMaintainer") {
074: public String getFunctionValue(Parameters arguments) {
075: return getMaintainer();
076: }
077: };
078:
079: /**
080: * Properties (initparameters) set by reading (or re-reading) the module configuration.
081: */
082: protected Map<String, String> properties;
083:
084: // the path to the Module configuration (xml) file, without the xml extension, and without the modules dir
085: protected String configurationPath;
086:
087: // the state map, containing runtime-generated information as name-value pairs.
088: private Map<String, String> states = new Hashtable<String, String>();
089:
090: // the name of the module maintainer
091: private String maintainer;
092:
093: // the module version
094: private int version;
095:
096: // startup call.
097: private boolean started = false;
098:
099: /**
100: * @deprecated
101: */
102: public Module() {
103: addFunction(getVersionFunction);
104: addFunction(getMaintainerFunction);
105: String startedAt = (new Date(System.currentTimeMillis()))
106: .toString();
107: setState(STATE_START_TIME, startedAt);
108: }
109:
110: public Module(String name) {
111: super (name);
112: addFunction(getVersionFunction);
113: addFunction(getMaintainerFunction);
114: String startedAt = (new Date(System.currentTimeMillis()))
115: .toString();
116: setState(STATE_START_TIME, startedAt);
117: }
118:
119: /**
120: * @since MMBase-1.8
121: */
122: public static ResourceLoader getModuleLoader() {
123: return ResourceLoader.getConfigurationRoot()
124: .getChildResourceLoader("modules");
125: }
126:
127: /**
128: * @since MMBase-1.8
129: */
130: public static ModuleReader getModuleReader(String configurationPath) {
131: try {
132: InputSource is = getModuleLoader().getInputSource(
133: configurationPath + ".xml");
134: if (is == null)
135: return null;
136: return new ModuleReader(is);
137: } catch (Exception e) {
138: log.error(e.getMessage(), e);
139: return null;
140: }
141: }
142:
143: /**
144: * @since MMBase-1.9
145: */
146: public ModuleReader getModuleReader() {
147: return Module.getModuleReader(configurationPath);
148: }
149:
150: /**
151: * Starts the module.
152: * This module calls the {@link #init()} of a module exactly once.
153: * In other words, once the init() is called, it does not call it again.
154: * This method is final and cannot be overridden.
155: * It is used to safely initialize modules during startup, and allows other modules
156: * to force the 'startup' of another module without risk.
157: */
158: public final void startModule() {
159: if (started)
160: return;
161: synchronized (Module.class) {
162: init();
163: started = true;
164: }
165: }
166:
167: /**
168: * Returns whether the module has started (has been initialized or is in
169: * its initialization fase).
170: */
171: public final boolean hasStarted() {
172: return started;
173: }
174:
175: /**
176: * Initializes the module.
177: * Init must be overridden to read the environment variables it needs.
178: * <br />
179: * This method is called by {@link #startModule()}, which makes sure it is not called
180: * more than once. You should not call init() directly, call startModule() instead.
181: */
182: public void init() {
183: }
184:
185: /**
186: * prepares the module when loaded.
187: * Onload must be overridden to execute methods that need to be performed when the module
188: * is loaded but before any other modules are initailized.
189: * <br />
190: * This method is called by {@link #startModules()}. You should not call onload() directly.
191: * @scope protected
192: */
193: public void onload() {
194: }
195:
196: /**
197: * Shuts down the module. This method is called by shutdownModules.
198: *
199: * @since MMBase-1.6.2
200: */
201: protected void shutdown() {
202: // on default, nothing needs to be done.
203: }
204:
205: /**
206: * Returns a state value by name.
207: * @since MMBase-1.9
208: */
209: public String getState(String name) {
210: return states.get(name);
211: }
212:
213: /**
214: * Sets a state value by name.
215: * @since MMBase-1.9
216: */
217: public String setState(String name, String value) {
218: return states.put(name, value);
219: }
220:
221: /**
222: * Returns the module's runtime-generated state information as a unmodifiable map with name-value pairs.
223: * @since MMBase-1.9
224: */
225: public Map<String, String> getStates() {
226: return Collections.unmodifiableMap(states);
227: }
228:
229: /**
230: * Sets an init-parameter key-value pair
231: */
232: public void setInitParameter(String key, String value) {
233: if (properties != null) {
234: properties.put(key, value);
235: }
236: }
237:
238: /**
239: * Gets an init-parameter key-value pair
240: */
241: public String getInitParameter(String key) {
242: if (properties != null) {
243: String value = properties.get(key);
244: if (value == null) {
245: key = key.toLowerCase();
246: value = properties.get(key);
247: // Can also set properties in web.xml/context.xml
248: if (value == null && MMBaseContext.isInitialized()
249: && MMBaseContext.getServletContext() != null) {
250: value = MMBaseContext.getServletContext()
251: .getInitParameter(getName() + "." + key);
252: }
253: // try the system property, set on the JVM commandline
254: // i.e. you could provide a value for the mmbaseroot "machinename" property by specifying:
255: // -Dmmbaseroot.machinename=myname
256: if (value == null) {
257: value = System.getProperty(getName() + "." + key);
258: }
259: }
260: return value;
261: } else {
262: log
263: .error("getInitParameters("
264: + key
265: + "): No properties found, called before they where loaded");
266: }
267: return null;
268: }
269:
270: /**
271: * Gets own modules properties
272: */
273: public Map<String, String> getInitParameters() {
274: return properties;
275: }
276:
277: /**
278: * Override properties through application context
279: * @since MMBase 1.8.5
280: */
281: public void loadInitParameters() {
282: loadInitParameters("mmbase/" + getName());
283: }
284:
285: /**
286: * Override properties through application context
287: * @param contextPath path in application context where properties are located
288: * @since MMBase 1.8.5
289: */
290: protected void loadInitParameters(String contextPath) {
291: try {
292: Map<String, String> contextMap = ApplicationContextReader
293: .getProperties(contextPath);
294: properties.putAll(contextMap);
295:
296: } catch (javax.naming.NamingException ne) {
297: log
298: .debug("Can't obtain properties from application context: "
299: + ne.getMessage());
300: }
301: }
302:
303: /**
304: * Returns an iterator of all the modules that are currently active.
305: * This function <code>null</code> if no attempt has the modules have (not) yet been to loaded.
306: * Unlike {@link #getModule}, this method does not automatically load modules if this hadn't occurred yet.
307: * @return an <code>Iterator</code> with all active modules
308: */
309: public static final Iterator<Module> getModules() {
310: if (modules == null) {
311: return null;
312: } else {
313: return modules.values().iterator();
314: }
315: }
316:
317: /**
318: * Provide some info on the module;
319: * By default, this returns the module description for the default locale
320: * @deprecated use getDescription
321: */
322: public String getModuleInfo() {
323: String value = getDescription();
324: if (value != null) {
325: return value;
326: } else {
327: return "No module info provided";
328: }
329: }
330:
331: /**
332: * maintainance call called by the admin module every x seconds.
333: */
334: public void maintainance() {
335: }
336:
337: /**
338: * Calls shutdown of all registered modules.
339: *
340: * @since MMBase-1.6.2
341: */
342: public static synchronized final void shutdownModules() {
343: if (modules != null) {
344: for (Module m : modules.values()) {
345: log.service("Shutting down " + m.getName());
346: m.shutdown();
347: log.service("Shut down " + m.getName());
348: }
349: }
350: modules = null;
351: }
352:
353: public static synchronized final void startModules() {
354: // call the onload to get properties
355: log.service("Starting modules " + modules.keySet());
356: for (Module mod : modules.values()) {
357: if (Thread.currentThread().isInterrupted()) {
358: log.info("Interrupted");
359: return;
360: }
361: if (log.isDebugEnabled()) {
362: log.debug("Starting module " + mod + "");
363: }
364: try {
365: mod.onload();
366: } catch (Exception f) {
367: log.warn("Exception in onload of module '" + mod
368: + "' ! " + f.getMessage(), f);
369: }
370: }
371: // so now really give em their init
372: if (log.isDebugEnabled()) {
373: log.debug("Initing the modules " + modules + "");
374: }
375: for (Module mod : modules.values()) {
376: if (Thread.currentThread().isInterrupted()) {
377: log.info("Interrupted");
378: return;
379: }
380: log.info("Starting module " + mod.getName());
381: try {
382: mod.startModule();
383: } catch (Exception f) {
384: log.error("Exception in startModule of module '" + mod
385: + "' ! " + f.getMessage(), f);
386: }
387: }
388: }
389:
390: /**
391: * @since MMBase-1.8.3
392: */
393: public static boolean hasModule(String name) {
394: boolean check = modules.containsKey(name.toLowerCase());
395: if (!check) {
396: check = modules.containsKey(name);
397: }
398: return check;
399: }
400:
401: /**
402: * Retrieves a reference to a Module.
403: * This call does not ensure that the requested module has been initialized.
404: *
405: * @param name the name of the module to retrieve
406: * @return a refernce to a <code>Module</code>, or <code>null</code> if the
407: * module does not exist or is inactive.
408: */
409: public static Module getModule(String name) {
410: return getModule(name, false);
411: }
412:
413: /**
414: * Since modules normally all have a different class, you can also obtain a module by its
415: * Class, in stead of by its name. The advantage is that you don't need to cast.
416: * @param clazz The class of the desired Module
417: * @return A Module instance or <code>null</code> if no such module.
418: * @since MMBase-1.9
419: */
420: public static <C extends Module> C getModule(Class<C> clazz) {
421: checkModules(true);
422: for (Module m : modules.values()) {
423: if (clazz.isInstance(m)) {
424: return (C) m;
425: }
426: }
427: return null;
428: }
429:
430: /**
431: * Makes sure that modules are loaded and started.
432: * @since MMBase-1.9
433: */
434: private static synchronized void checkModules(boolean startOnLoad) {
435: // are the modules loaded yet ? if not load them
436: if (modules == null) { // still null after obtaining lock
437: log.service("Loading MMBase modules...");
438: modules = loadModulesFromDisk();
439: if (log.isDebugEnabled()) {
440: log.debug("Modules not loaded, loading them..");
441: }
442: if (startOnLoad) {
443: startModules();
444: }
445: // also start the maintaince thread that calls all modules 'maintanance' method every x seconds
446: new ModuleProbe().start();
447:
448: }
449: }
450:
451: /**
452: * Retrieves a reference to a Module.
453: * If you set the <code>startOnLoad</code> to <code>true</code>,
454: * this call ensures that the requested module has been initialized by
455: * calling the {@link #startModule()} method.
456: * This is needed if you need to call Module methods from the init() of
457: * another module.
458: *
459: *
460: * @param name the name of the module to retrieve
461: * @param startOnLoad if true, the code makes sure the module has been started
462: * @return a reference to a <code>Module</code>, or <code>null</code> if the
463: * module does not exist or is inactive.
464: */
465: public static Module getModule(String name, boolean startOnLoad) {
466: checkModules(startOnLoad);
467: // try to obtain the ref to the wanted module
468: Module obj = modules.get(name.toLowerCase());
469: if (obj == null) { // try case sensitivily as well?
470: obj = modules.get(name);
471: }
472: if (obj != null) {
473: // make sure the module is started, as this method could
474: // have been called from the init() of another Module
475: if (startOnLoad) {
476: obj.startModule();
477: }
478: return obj;
479: } else {
480: log.warn("The module '" + name + "' could not be found!");
481: return null;
482: }
483: }
484:
485: public String getMaintainer() {
486: return maintainer;
487: }
488:
489: public void setMaintainer(String m) {
490: maintainer = m;
491: }
492:
493: public void setVersion(int v) {
494: version = v;
495: }
496:
497: public int getVersion() {
498: return version;
499: }
500:
501: /**
502: * Loads all module-xml present in <mmbase-config-dir>/modules.
503: * @return A HashTable with <module-name> --> Module-instance
504: */
505: private static synchronized Map<String, Module> loadModulesFromDisk() {
506: Map<String, Module> results = new HashMap<String, Module>();
507: ResourceLoader moduleLoader = getModuleLoader();
508: Collection<String> modules = moduleLoader.getResourcePaths(
509: ResourceLoader.XML_PATTERN, false/* non-recursive*/);
510: log.info("In " + moduleLoader
511: + " the following module XML's were found " + modules);
512: for (String file : modules) {
513: ModuleReader parser = null;
514: try {
515: InputSource is = moduleLoader.getInputSource(file);
516: if (is != null)
517: parser = new ModuleReader(is);
518: } catch (Exception e) {
519: log.error(e);
520: }
521: if (parser != null && parser.getStatus().equals("active")) {
522: // obtain module name. Use the filename of the xml if the name property is not set
523: String moduleName = parser.getName();
524: if (moduleName == null) {
525: moduleName = ResourceLoader.getName(file);
526: }
527: String className = parser.getClassName();
528: // try starting the module and give it its properties
529: try {
530: log.service("Loading module " + moduleName
531: + " with class " + className);
532: Module mod;
533: if (parser.getURLString() != null) {
534: log.service("loading module from jar "
535: + parser.getURLString());
536: URL url = new URL(parser.getURLString());
537: URLClassLoader c = new URLClassLoader(
538: new URL[] { url }, Module.class
539: .getClassLoader());
540: Class<?> newClass = c.loadClass(className);
541: try {
542: Constructor<?> constructor = newClass
543: .getConstructor(String.class);
544: mod = (Module) constructor
545: .newInstance(moduleName);
546: } catch (NoSuchMethodException nsme) {
547: log.warn(nsme);
548: mod = (Module) newClass.newInstance();
549: mod.setName(moduleName);
550: }
551: } else {
552: Class<?> newClass = Class.forName(className);
553: try {
554: Constructor<?> constructor = newClass
555: .getConstructor(String.class);
556: mod = (Module) constructor
557: .newInstance(moduleName);
558: } catch (NoSuchMethodException nsme) {
559: log
560: .service("Constructor with no name-argument is deprecated "
561: + nsme.getMessage());
562: mod = (Module) newClass.newInstance();
563: mod.setName(moduleName); // I think a module has one name.
564: }
565: }
566:
567: mod.configurationPath = ResourceLoader
568: .getName(file);
569:
570: results.put(moduleName, mod);
571:
572: mod.setMaintainer(parser.getMaintainer());
573: mod.setVersion(parser.getVersion());
574: parser.getLocalizedDescription(mod
575: .getLocalizedDescription());
576: parser.getLocalizedGUIName(mod
577: .getLocalizedGUIName());
578:
579: mod.properties = parser.getProperties();
580: mod.loadInitParameters();
581:
582: } catch (ClassNotFoundException cnfe) {
583: log.error("Could not load class with name '"
584: + className + "', "
585: + "which was specified in the module:'"
586: + file + " '(" + cnfe + ")");
587: } catch (Throwable e) {
588: log.error("Error while loading module class"
589: + Logging.stackTrace(e));
590: }
591: }
592: }
593: return results;
594: }
595:
596: }
|