0001: /*
0002:
0003: This software is OSI Certified Open Source Software.
0004: OSI Certified is a certification mark of the Open Source Initiative.
0005:
0006: The license (Mozilla version 1.0) can be read at the MMBase site.
0007: See http://www.MMBase.org/license
0008:
0009: */
0010: package org.mmbase.module.core;
0011:
0012: import java.io.File;
0013: import java.util.*;
0014: import java.text.DateFormat;
0015:
0016: import org.mmbase.core.event.*;
0017: import org.mmbase.datatypes.DataTypes;
0018: import org.mmbase.module.ProcessorModule;
0019: import org.mmbase.module.builders.DayMarkers;
0020: import org.mmbase.module.builders.Versions;
0021: import org.mmbase.module.corebuilders.*;
0022: import org.mmbase.security.MMBaseCop;
0023: import org.mmbase.storage.*;
0024: import org.mmbase.storage.search.RelationStep;
0025: import org.mmbase.model.*;
0026: import org.mmbase.storage.search.SearchQueryHandler;
0027: import org.mmbase.util.ResourceLoader;
0028: import org.mmbase.util.logging.Logger;
0029: import org.mmbase.util.logging.Logging;
0030: import org.mmbase.util.xml.BuilderReader;
0031: import org.mmbase.util.xml.BuilderWriter;
0032: import org.mmbase.util.functions.*;
0033: import org.xml.sax.SAXException;
0034:
0035: import java.util.concurrent.ConcurrentHashMap;
0036:
0037: /**
0038: * The module which provides access to the MMBase storage defined
0039: * by the provided name/setup.
0040: * It holds the overal object cloud made up of builders, objects and relations and
0041: * all the needed tools to use them.
0042: *
0043: * @author Daniel Ockeloen
0044: * @author Pierre van Rooden
0045: * @author Johannes Verelst
0046: * @author Ernst Bunders
0047: * @version $Id: MMBase.java,v 1.236 2008/03/11 16:48:02 michiel Exp $
0048: */
0049: public class MMBase extends ProcessorModule {
0050:
0051: /**
0052: * State of MMBase after shutdown
0053: * @since MMBase-1.7
0054: */
0055: private static final int STATE_SHUT_DOWN = -3;
0056:
0057: /**
0058: * State of MMBase before the beginning of startup
0059: * @since MMBase-1.6
0060: */
0061: private static final int STATE_START_UP = -2;
0062:
0063: /**
0064: * State of MMBase at the start of the initialization
0065: * @since MMBase-1.8
0066: */
0067: private static final int STATE_STARTED_INIT = -1;
0068:
0069: /**
0070: * State of MMBase before builders are loaded
0071: * @since MMBase-1.6
0072: */
0073: private static final int STATE_LOAD = 0;
0074: /**
0075: * State of MMBase before builders are initialized
0076: * @since MMBase-1.6
0077: */
0078: private static final int STATE_INITIALIZE = 1;
0079: /**
0080: * State of MMBase after startup is completed
0081: * @since MMBase-1.6
0082: */
0083: private static final int STATE_UP = 2;
0084:
0085: // logging
0086: private static final Logger log = Logging
0087: .getLoggerInstance(MMBase.class);
0088:
0089: /**
0090: * Reference to the MMBase singleton. Used for quick reference by getMMBase();
0091: */
0092: private static MMBase mmbaseroot = null;
0093:
0094: /**
0095: * Time in seconds, when mmbase was started.
0096: * @since MMBase-1.7
0097: */
0098: public static final int startTime = (int) (System
0099: .currentTimeMillis() / 1000);
0100:
0101: /**
0102: * Base name for the storage to be accessed using this instance of MMBase.
0103: * Retrieved from the mmbaseroot module configuration.
0104: * If not specified the default is "def1".
0105: * Should be made private and accessed instead using getBaseName()
0106: * @scope private
0107: */
0108: public String baseName = "def1";
0109:
0110: /**
0111: * Reference to the TypeDef builder.
0112: */
0113: private TypeDef typeDef;
0114: /**
0115: * Reference to the RelDef builder.
0116: */
0117: private RelDef relDef;
0118: /**
0119: * Reference to the OALias builder.
0120: */
0121: private OAlias oAlias;
0122: /**
0123: * Reference to the InsRel builder.
0124: */
0125: private InsRel insRel;
0126: /**
0127: * Reference to the TypeRel builder.
0128: */
0129: private TypeRel typeRel;
0130:
0131: /**
0132: * The map that contains all loaded builders. Includes virtual builders.
0133: * A collection of builders from this map can be accessed by calling {@link #getBuilders}
0134: */
0135: private final Map<String, MMObjectBuilder> mmobjs = new ConcurrentHashMap<String, MMObjectBuilder>();
0136:
0137: private CloudModel cloudModel;
0138:
0139: /**
0140: * Determines whether MMBase is in development mode.
0141: * @see #inDevelopment()
0142: * @since MMBase-1.8.1
0143: */
0144: private boolean inDevelopment = false;
0145:
0146: /**
0147: * Name of the machine used in the mmbase cluster.
0148: * it is used for the mmservers objects. Make sure that this is different
0149: * for each node in your cluster. This is not the machines dns name
0150: * (as defined by host as name or ip number).
0151: */
0152: private String machineName = "unknown";
0153:
0154: /**
0155: * The host or ip number of the machine this module is
0156: * running on. Its important that this name is set correctly because it is
0157: * used for communication between mmbase nodes and external devices
0158: */
0159: private String host = "unknown";
0160:
0161: /**
0162: * Authorisation type. Access using getAuthType()
0163: */
0164: private String authtype = "none";
0165:
0166: /**
0167: * The storage manager factory to use. Retrieve using getStorageManagerFactory();
0168: */
0169: private StorageManagerFactory storageManagerFactory = null;
0170:
0171: /**
0172: * Reference to the Root builder (the most basic builder, aka 'object').
0173: * This can be null (does not exist) in older systems
0174: */
0175: private MMObjectBuilder rootBuilder;
0176:
0177: /**
0178: * our securityManager (MMBaseCop)
0179: */
0180: private MMBaseCop mmbaseCop = null;
0181:
0182: /**
0183: * Reference to the cluster builder, a virtual builder used to perform
0184: * multilevel searches.
0185: * @see ClusterBuilder
0186: */
0187: private ClusterBuilder clusterBuilder;
0188:
0189: /**
0190: * Currently used locale. Access using getLocale()
0191: */
0192: private Locale locale = Locale.ENGLISH;
0193:
0194: private TimeZone timeZone = TimeZone.getDefault();
0195:
0196: /**
0197: * Currently used encoding. Access using getEncoding(). This
0198: * default to ISO-8859-1 as long as support for other encodings is
0199: * not thoroughly tested. In the feature we will probably switch
0200: * to UTF-8.
0201: *
0202: * @since MMBase-1.6
0203: */
0204: private String encoding = "ISO-8859-1";
0205:
0206: /**
0207: * MMBase 'up state. Access using getState()
0208: *
0209: * @since MMBase-1.6
0210: */
0211: private int mmbaseState = STATE_START_UP;
0212:
0213: /**
0214: * This set contains the names of buidlers that are in the process of being loaded.
0215: * The map is used to prevent circular references when extending builders.
0216: *
0217: * @since MMBase-1.6
0218: */
0219: private Set<String> loading = new HashSet<String>();
0220:
0221: /**
0222: * Constructor to create the MMBase root module.
0223: */
0224: public MMBase(String name) {
0225: super (name);
0226: if (mmbaseroot != null)
0227: log.error("Tried to instantiate a second MMBase");
0228: log.debug("MMBase constructed");
0229: }
0230:
0231: /**
0232: * This method tries to configure the persistence directory of OSCache, if possible (OSCache is
0233: * available, and necessary (no 'cache.path' property is configured for OSCache). Then, a
0234: * directory named 'oscache' in the mmbase data directory is used to set the 'cache.path'
0235: * property of the ServletCacheAdminidstrator class of OSCache.
0236: * @since MMBase-1.9
0237: */
0238: protected void configureOSCache() {
0239: try {
0240: Properties p = new Properties();
0241: java.io.InputStream is = getClass().getClassLoader()
0242: .getResourceAsStream("oscache.properties");
0243: if (is != null) {
0244: p.load(is);
0245: }
0246: if (p.getProperty("cache.path") == null
0247: || "".equals(p.getProperty("cache.path"))) {
0248: p.setProperty("cache.path", getInitParameter("datadir")
0249: + java.io.File.separator + "oscache");
0250: }
0251:
0252: Class osCache = Class
0253: .forName("com.opensymphony.oscache.web.ServletCacheAdministrator");
0254: java.lang.reflect.Method m = osCache.getMethod(
0255: "getInstance", javax.servlet.ServletContext.class,
0256: Properties.class);
0257: m.invoke(null, MMBaseContext.getServletContext(), p);
0258: log.service("Using " + p + " for oscache");
0259: } catch (Throwable e) {
0260: log.service(e.getMessage());
0261: }
0262: }
0263:
0264: /**
0265: * Initalizes the MMBase module. Evaluates the parameters loaded from the configuration file.
0266: * Sets parameters (authorisation, language), loads the builders, and starts MultiCasting.
0267: */
0268: public void init() {
0269: //synchronized(MMBase.class) {
0270: if (mmbaseState >= STATE_STARTED_INIT) {
0271: log.debug("Already initialized or initializing");
0272: return;
0273: } else if (mmbaseState == STATE_SHUT_DOWN) {
0274: log.warn("was shutdown... should NOT restart again!");
0275: return;
0276: }
0277: log.service("Init of " + org.mmbase.Version.get() + " (" + this
0278: + ")");
0279:
0280: configureOSCache();
0281:
0282: mmbaseState = STATE_STARTED_INIT;
0283:
0284: // Set the mmbaseroot singleton var
0285: // This prevents recursion if MMBase.getMMBase() is called while
0286: // this method is run
0287: mmbaseroot = this ;
0288:
0289: super .init();
0290:
0291: // is there a basename defined in MMBASE.properties ?
0292: String tmp = getInitParameter("BASENAME");
0293: if (tmp != null) {
0294: // yes then replace the default name (def1)
0295: baseName = tmp;
0296: } else {
0297: log
0298: .info("init(): No name defined for mmbase using default (def1)");
0299: }
0300:
0301: tmp = getInitParameter("AUTHTYPE");
0302: if (tmp != null && !tmp.equals("")) {
0303: authtype = tmp;
0304: }
0305:
0306: tmp = getInitParameter("TIMEZONE");
0307: if (tmp != null && !tmp.equals("")) {
0308: timeZone = TimeZone.getTimeZone(tmp);
0309: }
0310: DateFormat format = DateFormat.getDateTimeInstance(
0311: DateFormat.LONG, DateFormat.LONG, Locale.US);
0312: format.setTimeZone(timeZone);
0313: log.info("MMBase Time zone : "
0314: + timeZone.getDisplayName(Locale.US) + " (it's now "
0315: + format.format(new Date()) + ")");
0316: org.mmbase.util.dateparser.DateParser.setDefault(timeZone);
0317:
0318: tmp = getInitParameter("LANGUAGE");
0319: if (tmp != null && !tmp.equals("")) {
0320: locale = new Locale(tmp, "");
0321: }
0322: log.info("MMBase default locale : " + locale);
0323: org.mmbase.util.LocalizedString.setDefault(locale);
0324:
0325: tmp = getInitParameter("DEVELOPMENT");
0326: if (tmp != null && !tmp.equals("")) {
0327: inDevelopment = "true".equals(tmp);
0328: }
0329:
0330: tmp = getInitParameter("ENCODING");
0331: if (tmp != null && !tmp.equals("")) {
0332: encoding = tmp;
0333: }
0334: org.mmbase.util.XMLEntityResolver.clearMMEntities(false);
0335:
0336: // default locale has to be known before initializing datatypes:
0337: DataTypes.initialize();
0338:
0339: String localHost;
0340: try {
0341: localHost = java.net.InetAddress.getLocalHost()
0342: .getHostName();
0343: } catch (java.net.UnknownHostException uhe) {
0344: localHost = "localhost";
0345: }
0346:
0347: log.service("Localhost: " + localHost);
0348:
0349: tmp = getInitParameter("HOST");
0350: if (tmp != null && !tmp.equals("")) {
0351: host = tmp;
0352: } else {
0353: host = localHost;
0354: }
0355:
0356: org.mmbase.util.XMLEntityResolver.clearMMEntities(false);
0357:
0358: String machineNameParam = getInitParameter("MACHINENAME");
0359: if (machineNameParam != null) {
0360: // try to incorporate the hostname (if needed)
0361: int pos = machineNameParam.indexOf("${HOST}");
0362: if (pos != -1) {
0363: machineNameParam = machineNameParam.substring(0, pos)
0364: + machineName
0365: + machineNameParam.substring(pos + 7);
0366: }
0367: // you may also try to incorporate the username in the machine name
0368: pos = machineNameParam.indexOf("${USER}");
0369: if (pos != -1) {
0370: machineNameParam = machineNameParam.substring(0, pos)
0371: + System.getProperty("user.name")
0372: + machineNameParam.substring(pos + 7);
0373: }
0374:
0375: pos = machineNameParam.indexOf("${CONTEXT}");
0376: if (pos != -1) {
0377: if (!MMBaseContext.htmlRootInitialized) {
0378: log
0379: .warn("HTML root not yet known. MachineName will not be correct yet.");
0380: }
0381: machineNameParam = machineNameParam.substring(0, pos)
0382: + MMBaseContext.getHtmlRootUrlPath()
0383: + machineNameParam.substring(pos + 10);
0384: }
0385:
0386: machineName = machineNameParam;
0387: } else {
0388: if (!MMBaseContext.htmlRootInitialized) {
0389: log
0390: .warn("HTML root not yet known. MachineName will not be correct yet.");
0391: }
0392: // default machine name is the local host name plus context-path.
0393: // We suppose that that is sufficiently unique in most cases
0394: machineName = localHost
0395: + MMBaseContext.getHtmlRootUrlPath();
0396:
0397: }
0398: log.service("MMBase machine name used for clustering: '"
0399: + machineName + "'");
0400: Logging.setMachineName(machineName);
0401: org.mmbase.util.XMLEntityResolver.clearMMEntities(false);
0402:
0403: log.service("Initializing storage");
0404: initializeStorage();
0405:
0406: mmbaseState = STATE_LOAD;
0407:
0408: log.debug("Loading builders:");
0409:
0410: cloudModel = ModelsManager.addModel("default", "default.xml");
0411:
0412: loadBuilders();
0413:
0414: if (Thread.currentThread().isInterrupted()) {
0415: log.info("Interrupted");
0416: return;
0417: }
0418:
0419: mmbaseState = STATE_INITIALIZE;
0420:
0421: log.debug("Checking MMBase");
0422: if (!checkMMBase()) {
0423: // there is no base defined yet, create the core objects
0424: createMMBase();
0425: }
0426:
0427: log.service("Initializing builders:");
0428: org.mmbase.util.XMLEntityResolver.clearMMEntities(false);
0429:
0430: initBuilders();
0431:
0432: EventManager.getInstance().addEventListener(
0433: org.mmbase.cache.NodeCache.getCache());
0434:
0435: log.debug("Objects started");
0436:
0437: String writerpath = getInitParameter("XMLBUILDERWRITERDIR");
0438: if (writerpath != null && !writerpath.equals("")) {
0439: for (MMObjectBuilder builder : getBuilders()) {
0440: if (!builder.isVirtual()) {
0441: String name = builder.getTableName();
0442: log.debug("WRITING BUILDER FILE =" + writerpath
0443: + File.separator + name);
0444: try {
0445: BuilderWriter builderOut = new BuilderWriter(
0446: builder);
0447: builderOut.setIncludeComments(false);
0448: builderOut.setExpandBuilder(false);
0449: builderOut.writeToFile(writerpath
0450: + File.separator
0451: + builder.getTableName() + ".xml");
0452: } catch (Exception ex) {
0453: log.error(Logging.stackTrace(ex));
0454: }
0455: }
0456: }
0457: }
0458: if (Thread.currentThread().isInterrupted()) {
0459: log.info("Interrupted");
0460: return;
0461: }
0462:
0463: org.mmbase.util.XMLEntityResolver.clearMMEntities(false);
0464: // try to load security...
0465: try {
0466: mmbaseCop = new MMBaseCop();
0467: } catch (Exception e) {
0468: log
0469: .fatal("Error loading the mmbase cop: "
0470: + e.getMessage());
0471: log.error(Logging.stackTrace(e));
0472: log.error("MMBase will continue without security.");
0473: log.error("All future security invocations will fail.");
0474: }
0475: org.mmbase.util.XMLEntityResolver.clearMMEntities(true);
0476: typeRel.readCache();
0477:
0478: // signal that MMBase is up and running
0479: mmbaseState = STATE_UP;
0480: log.info("MMBase is up and running");
0481: //notifyAll();
0482: //}
0483: }
0484:
0485: // javadoc inherited
0486: public void shutdown() {
0487: mmbaseState = STATE_SHUT_DOWN;
0488:
0489: // there all over the place static references to mmbasroot are maintained, which I cannot
0490: // change presently. so let's clean up mmbaseroot itself as well as possible...
0491: typeDef = null;
0492: relDef = null;
0493: oAlias = null;
0494: insRel = null;
0495: typeRel = null;
0496: mmobjs.clear();
0497: cloudModel = null;
0498: storageManagerFactory = null;
0499: rootBuilder = null;
0500: mmbaseCop = null;
0501: clusterBuilder = null;
0502: mmbaseroot = null;
0503: org.mmbase.util.ThreadPools.shutdown();
0504: org.mmbase.core.event.EventManager.getInstance().shutdown();
0505: }
0506:
0507: /**
0508: * @since MMBase-1.7
0509: */
0510: public boolean isShutdown() {
0511: return mmbaseState == STATE_SHUT_DOWN;
0512: }
0513:
0514: /**
0515: * Returns <code>true</code> when MMBase is in development mode.
0516: * This can be used to determine behavior with regards to common errors,
0517: * such as whether or not to throw an exception when a non-existing field
0518: * in a buidler is referenced.
0519: * The value for this property ('true' or 'false') can be set in the "development"
0520: * property in the mmbaseroot.xml configuration file.
0521: * The default value is <code>false</code>.
0522: * @since MMBase-1.8.1
0523: */
0524: public boolean inDevelopment() {
0525: return inDevelopment;
0526: }
0527:
0528: /**
0529: * Checks whether the storage to be used exists.
0530: * The system determines whether the object table exists
0531: * for the baseName provided in the configuration file.
0532: * @return <code>true</code> if the storage exists and is accessible, <code>false</code> otherwise.
0533: */
0534: boolean checkMMBase() {
0535: return getStorageManager().exists();
0536: }
0537:
0538: /**
0539: * Create a new MMBase persistent storage instance.
0540: * The storage instance created is based on the baseName provided in the configuration file.
0541: * This call automatically creates an object table.
0542: * The fields in the table are either specified in an object builder xml,
0543: * or from a default setup existing of a number field and a owner field.
0544: * Note: If specified, the object builder is instantiated and its table created, but
0545: * the builder is not registered in the TypeDef builder, as this builder does not exist yet.
0546: * Registration happens when the other builders are registered.
0547: * @return <code>true</code> if the storage was succesfully created, otherwise a runtime exception is thrown
0548: * (shouldn't it return <code>false</code> instead?)
0549: */
0550: boolean createMMBase() {
0551: log.debug(" creating new multimedia base : " + baseName);
0552: getStorageManager().create();
0553: return true;
0554: }
0555:
0556: /**
0557: * Determines whether a builder is in the process of being loaded,
0558: * but not yet finished. Needed to track down circular references.
0559: * @return true if the builder is being loaded
0560: *
0561: * @since MMBase-1.6
0562: */
0563: private boolean builderLoading(String name) {
0564: return loading.contains(name);
0565: }
0566:
0567: /**
0568: * Retrieves a specified builder.
0569: * If the builder is not loaded, but the system is in the 'startup' state
0570: * (i.e. it is in the process of loading builders), an attempt is made to
0571: * directly load the builder.
0572: * This allows for dependencies between builders to exist (i.e. inheritance).
0573: * When circular reference occurs between two loading buidlers, an exception is thrown.
0574: *
0575: * @since MMBase-1.6
0576: * @param name The name of the builder to retrieve
0577: * @return a <code>MMObjectBuilder</code> for the specified builder
0578: * @throws CircularReferenceException when circular reference is detected
0579: * @throws BuilderConfigurationException if the builder config file does not exist
0580: */
0581: public MMObjectBuilder getBuilder(String name)
0582: throws CircularReferenceException {
0583: MMObjectBuilder builder = getMMObject(name);
0584: if (builder == null && (mmbaseState == STATE_LOAD)) {
0585: // MM: odd way to check this. Could it not be done a bit more explicitely?
0586: if (builderLoading(name)) {
0587: throw new CircularReferenceException(
0588: "Circular reference to builder with name '"
0589: + name + "': currently loading "
0590: + loading);
0591: }
0592: builder = loadBuilder(name);
0593: }
0594: return builder;
0595: }
0596:
0597: public String getBuilderNameForNode(final int number) {
0598: int nodeType = getMMBase().getStorageManager().getNodeType(
0599: number);
0600: if (nodeType < 0) {
0601: // the node does not exists, which according to javadoc should return null
0602: throw new StorageNotFoundException(
0603: "Cannot determine node type of node with number ="
0604: + number);
0605: }
0606: // if the type is not for the current builder, determine the real builder
0607: return getTypeDef().getValue(nodeType);
0608: }
0609:
0610: public MMObjectBuilder getBuilderForNode(final int number) {
0611: String builderName = getBuilderNameForNode(number);
0612: MMObjectBuilder nodeBuilder = null;
0613: if (builderName == null) {
0614: log.error("The nodetype name of node #" + number
0615: + " could not be found '");
0616: } else {
0617: nodeBuilder = getBuilder(builderName);
0618: if (nodeBuilder == null) {
0619: log.warn("Node #" + number + "'s builder "
0620: + builderName
0621: + " is not loaded, taking 'object'.");
0622: nodeBuilder = getBuilder("object");
0623: }
0624: }
0625: return nodeBuilder;
0626: }
0627:
0628: /**
0629: * @since MMBase-1.8
0630: */
0631: public MMObjectBuilder addBuilder(String name, MMObjectBuilder bul) {
0632: if (bul == null)
0633: throw new IllegalArgumentException("Builder '" + name
0634: + "' was null");
0635: return mmobjs.put(name, bul);
0636: }
0637:
0638: /**
0639: * Retrieves a specified builder.
0640: * @scope protected
0641: * @param name The name of the builder to retrieve
0642: * @return a <code>MMObjectBuilder</code> if found, <code>null</code> otherwise
0643: */
0644: public MMObjectBuilder getMMObject(String name) {
0645: if (name == null)
0646: throw new RuntimeException(
0647: "Cannot get builder with name 'NULL' in "
0648: + machineName);
0649: MMObjectBuilder o = mmobjs != null ? mmobjs.get(name) : null;
0650: if (o == null) {
0651: log.trace("MMObject " + name + " could not be found"); // can happen...
0652: }
0653: return o;
0654: }
0655:
0656: /**
0657: * Retrieves the MMBase module('mmbaseroot').
0658: * @return the active MMBase module
0659: */
0660: public static MMBase getMMBase() {
0661: if (mmbaseroot == null) {
0662: synchronized (MMBase.class) { // make sure only one mmbaseroot is instantiated (synchronized on random static member...)
0663: mmbaseroot = getModule(MMBase.class);
0664: if (mmbaseroot == null) {
0665: log
0666: .fatal("The mmbaseroot module could not be found. Perhaps 'mmbaseroot.xml' is missing?");
0667: } else {
0668: mmbaseroot.startModule();
0669: }
0670: //MMBase.class.notifyAll();
0671: }
0672: }
0673: return mmbaseroot;
0674: }
0675:
0676: /**
0677: * Retrieves the loaded security manager(MMBaseCop).
0678: * @return the loaded security manager(MMBaseCop)
0679: */
0680: public MMBaseCop getMMBaseCop() {
0681: return mmbaseCop;
0682: }
0683:
0684: /**
0685: * Retrieves a Collection of loaded builders.
0686: */
0687: public Collection<MMObjectBuilder> getBuilders() {
0688: return mmobjs.values();
0689: }
0690:
0691: /**
0692: * Returns a reference to the InsRel builder.
0693: * @return the <code>InsRel</code> builder if defined, <code>null</code> otherwise
0694: */
0695: public InsRel getInsRel() {
0696: return insRel;
0697: }
0698:
0699: /**
0700: * Returns a reference to the RelDef builder.
0701: * @return the <code>RelDef</code> builder if defined, <code>null</code> otherwise
0702: */
0703: public RelDef getRelDef() {
0704: return relDef;
0705: }
0706:
0707: /**
0708: * Returns a reference to the TypeDef builder.
0709: * @return the <code>TypeDef</code> builder if defined, <code>null</code> otherwise
0710: */
0711: public TypeDef getTypeDef() {
0712: return typeDef;
0713: }
0714:
0715: /**
0716: * Returns a reference to the TypeRel builder.
0717: * @return the <code>TypeRel</code> builder if defined, <code>null</code> otherwise
0718: */
0719: public TypeRel getTypeRel() {
0720: return typeRel;
0721: }
0722:
0723: /**
0724: * Returns a reference to the OAlias builder.
0725: * @return the <code>OAlias</code> builder if defined, <code>null</code> otherwise
0726: */
0727: public OAlias getOAlias() {
0728: return oAlias;
0729: }
0730:
0731: /**
0732: * Returns a reference to the Object builder.
0733: * The Object builder is the builder from which all other builders eventually extend.
0734: * @return the <code>Object</code> builder.
0735: * @since MMBase-1.6
0736: */
0737: public MMObjectBuilder getRootBuilder() {
0738: if (rootBuilder == null) {
0739: rootBuilder = loadBuilder("object");
0740: }
0741: return rootBuilder;
0742: }
0743:
0744: /**
0745: * Returns the otype of the Object builder, or -1 if it is not known.
0746: * The Object builder is the builder from which all other builders eventually extend.
0747: * @since MMBase-1.6
0748: */
0749: public int getRootType() {
0750: if (rootBuilder == null) {
0751: return -1;
0752: } else {
0753: return rootBuilder.getNumber();
0754: }
0755: }
0756:
0757: /**
0758: * Returns a reference to the cluster builder, a virtual builder used to
0759: * perform multilevel searches.
0760: *
0761: * @return The cluster builder.
0762: * @see ClusterBuilder
0763: */
0764: public ClusterBuilder getClusterBuilder() {
0765: assertUp();
0766: return clusterBuilder;
0767: }
0768:
0769: /**
0770: * Locks until init of mmbase is finished.
0771: * @since MMBase-1.7
0772: */
0773: protected void assertUp() {
0774: if (!getState()) {
0775: synchronized (this ) {
0776: // lock until up. (Init is synchronized on this too)
0777: }
0778: }
0779: }
0780:
0781: /**
0782: * Retrieves the storage base name
0783: * @return the base name as a <code>String</code>
0784: */
0785: public String getBaseName() {
0786: return baseName;
0787: }
0788:
0789: /**
0790: * Performs periodic maintenance.
0791: */
0792: public void maintainance() {
0793: DayMarkers dayMarkers = (DayMarkers) getBuilder("daymarks");
0794: if (dayMarkers != null) {
0795: dayMarkers.probe();
0796: } else {
0797: log.error("Can't access builder : daymarks");
0798: }
0799: }
0800:
0801: /**
0802: * Retrieves the machine name.
0803: * This value is set using the configuration file.
0804: * @return the machine name as a <code>String</code>
0805: */
0806: public String getMachineName() {
0807: return machineName;
0808: }
0809:
0810: /**
0811: * Retrieves the host name or ip number
0812: * This value is set using the configuration file.
0813: * @return the host name as a <code>String</code>
0814: */
0815: public String getHost() {
0816: return host;
0817: }
0818:
0819: /**
0820: * Loads a core Builder.
0821: * If the builder does not exist, an exception is thrown.
0822: * @since MMBase-1.6
0823: * @param name the name of the builder to load
0824: * @return the builder
0825: * @throws BuilderConfigurationException if the builder config file does not exist or is inactive
0826: */
0827: private MMObjectBuilder loadCoreBuilder(String name) {
0828: MMObjectBuilder builder = loadBuilder(name);
0829: if (builder == null) {
0830: throw new BuilderConfigurationException("The core builder "
0831: + name + " is mandatory but inactive.");
0832: } else {
0833: log.debug("Loaded core builder " + builder + " with otype "
0834: + builder.getNumber());
0835: return builder;
0836: }
0837: }
0838:
0839: /**
0840: *@since MMBase-1.7
0841: */
0842: private void loadBuilders() {
0843: // first load the core builders
0844: // remarks:
0845: // - If nodescaches inactive, in init of typerel reldef nodes are created wich uses InsRel.getNumber(), so typerel must be started after insrel and reldef. (bug #6237)
0846:
0847: getRootBuilder(); // loads object.xml if present.
0848:
0849: typeDef = (TypeDef) loadCoreBuilder("typedef");
0850: relDef = (RelDef) loadCoreBuilder("reldef");
0851: insRel = (InsRel) loadCoreBuilder("insrel");
0852: typeRel = (TypeRel) loadCoreBuilder("typerel");
0853:
0854: try {
0855: oAlias = (OAlias) loadBuilder("oalias");
0856: } catch (BuilderConfigurationException e) {
0857: // OALias builder was not defined -
0858: // builder is optional, so this is not an error
0859: }
0860:
0861: Set<String> builders = getBuilderLoader().getResourcePaths(
0862: ResourceLoader.XML_PATTERN, true/* recursive*/);
0863:
0864: log.info("Loading builders: " + builders);
0865: for (String builderXml : builders) {
0866: if (Thread.currentThread().isInterrupted()) {
0867: return;
0868: }
0869: String resourceName = ResourceLoader.getName(builderXml);
0870: String resourceDirectory = ResourceLoader
0871: .getDirectory(builderXml)
0872: + "/";
0873: loadBuilderFromXML(resourceName, resourceDirectory);
0874: if (cloudModel != null) {
0875: cloudModel.addBuilder(resourceName, "builders/"
0876: + resourceDirectory + resourceName + ".xml");
0877: }
0878: }
0879:
0880: log.debug("Starting Cluster Builder");
0881: clusterBuilder = new ClusterBuilder(this );
0882: }
0883:
0884: /**
0885: * Initializes the builders, using the builder xml files in the config directory
0886: * @return Always <code>true</code>
0887: */
0888: boolean initBuilders() {
0889:
0890: typeDef.init();
0891:
0892: // first initialize versions, if available (table must exist for quereis to succeed)
0893: log.debug("Versions:");
0894: Versions versions = (Versions) getBuilder("versions");
0895: if (versions != null) {
0896: versions.init();
0897: }
0898:
0899: relDef.init();
0900: insRel.init();
0901: typeRel.init();
0902:
0903: log.debug("mmobjects, inits");
0904: for (Iterator<MMObjectBuilder> bi = getBuilders().iterator(); bi
0905: .hasNext();) {
0906: MMObjectBuilder builder = bi.next();
0907: log.debug("init " + builder);
0908: try {
0909: initBuilder(builder);
0910: } catch (BuilderConfigurationException e) {
0911: // something bad with this builder or its parents - remove it
0912: log
0913: .error("Removed builder "
0914: + builder.getTableName()
0915: + " from the builderlist, as it cannot be initialized.");
0916: bi.remove();
0917: } catch (Exception ex) {
0918: log
0919: .error("Something went wrong while initializing builder "
0920: + builder.getTableName());
0921: log
0922: .info("This builder will be removed from active builder list");
0923: log.error(Logging.stackTrace(ex));
0924: bi.remove();
0925: }
0926: }
0927:
0928: log.debug("**** end of initBuilders");
0929: return true;
0930: }
0931:
0932: /**
0933: * inits a builder
0934: * @param builder The builder which has to be initialized
0935: */
0936: public void initBuilder(MMObjectBuilder builder) {
0937: if (!builder.isVirtual()) {
0938: builder.init();
0939: typeDef.loadTypeDef(builder.getTableName());
0940: Versions versions = (Versions) getBuilder("versions");
0941: if (versions != null && versions.created()) {
0942: checkBuilderVersion(builder.getTableName(), versions);
0943: }
0944: }
0945: }
0946:
0947: /**
0948: * Unloads a builders from MMBase. After this, the builder is gone
0949: * @param builder the builder which has to be unloaded
0950: */
0951: public void unloadBuilder(MMObjectBuilder builder) {
0952: if (mmobjs.remove(builder.getTableName()) == null) {
0953: throw new RuntimeException(
0954: "builder with name: "
0955: + builder.getTableName()
0956: + " could not be unloaded, since it was not loaded.");
0957: }
0958: if (!builder.isVirtual()) {
0959: typeDef.unloadTypeDef(builder.getTableName());
0960: log.info("unloaded builder with name:"
0961: + builder.getTableName());
0962: } else {
0963: log.info("unloaded virtual builder with name:"
0964: + builder.getTableName());
0965: }
0966: }
0967:
0968: /**
0969: * The (base)path to the builder configuration files
0970: * @since MMBase-1.8
0971: */
0972: public ResourceLoader getBuilderLoader() {
0973: return ResourceLoader.getConfigurationRoot()
0974: .getChildResourceLoader("builders");
0975: }
0976:
0977: /**
0978: * @since MMBase-1.8
0979: */
0980: public BuilderReader getBuilderReader(String builderName) {
0981: try {
0982: java.net.URL url = getBuilderLoader().getResource(
0983: builderName + ".xml");
0984: if (url == null)
0985: return null;
0986: org.w3c.dom.Document doc = ResourceLoader.getDocument(url,
0987: true, BuilderReader.class);
0988: BuilderReader r = new BuilderReader(doc, this );
0989: r.setSystemId(url.toString());
0990: return r;
0991: } catch (SAXException se) {
0992: log.error(se);
0993: return null;
0994: } catch (java.io.IOException ioe) {
0995: log.error(ioe);
0996: return null;
0997: }
0998: }
0999:
1000: /**
1001: * Locate one specific builder withing the main builder config path, including sub-paths.
1002: * If the builder already exists, the existing object is returned instead.
1003: * If the builder cannot be found in this path, a BuilderConfigurationException is thrown.
1004: * @since MMBase-1.6
1005: * @param builderName name of the builder to initialize
1006: * @return the initialized builder object, or null if the builder could not be created (i.e. is inactive).
1007: * @throws BuilderConfigurationException if the builder config file does not exist
1008: */
1009: synchronized MMObjectBuilder loadBuilder(String builderName) { // synchronized to make sure that storage initialized only once
1010: return loadBuilder(builderName, "");
1011: }
1012:
1013: /**
1014: * Locate one specific builder within a given path, relative to the main builder config path, including sub-paths.
1015: * Return the actual path.
1016: * @param builderName name of the builder to find
1017: * @param path the path to start searching. The path need be closed with a '/ character
1018: * @return the file path to the builder xml, or null if the builder could not be created (i.e. is inactive).
1019: * @throws BuilderConfigurationException if the builder config file does not exist
1020: * @todo The second argument (and perhaps the whole function) is silly, only exists because this
1021: * function used to be implemented recursively (now delegated to ResourceLoader).
1022: */
1023: public String getBuilderPath(String builderName, String path) {
1024: Set<String> builders = getBuilderLoader()
1025: .getResourcePaths(
1026: java.util.regex.Pattern.compile(path
1027: + ResourceLoader.XML_PATTERN.pattern()),
1028: true /*recursive*/);
1029: if (log.isDebugEnabled()) {
1030: log.debug("Found builder " + builders + " from "
1031: + getBuilderLoader() + " searching for "
1032: + builderName);
1033: }
1034: String xml = builderName + ".xml";
1035: for (String builderXml : builders) {
1036: if (builderXml.equals(xml)) {
1037: return "";
1038: } else if (builderXml.endsWith("/" + xml)) {
1039: return builderXml.substring(0, builderXml.length()
1040: - xml.length());
1041: }
1042: }
1043: return null;
1044: }
1045:
1046: /**
1047: * Locate one specific builder within a given path, relative to the main builder config path, including sub-paths.
1048: * If the builder already exists, the existing object is returned instead.
1049: * @param builderName name of the builder to initialize
1050: * @param ipath the path to start searching. The path need be closed with a File.seperator character.
1051: * @return the initialized builder object, or null if the builder could not be created (i.e. is inactive).
1052: * @throws BuilderConfigurationException if the builder config file does not exist
1053: */
1054: MMObjectBuilder loadBuilder(String builderName, String ipath) {
1055: MMObjectBuilder builder = getMMObject(builderName);
1056: if (builder != null) {
1057: log
1058: .debug("Builder '" + builderName
1059: + "' is already loaded");
1060: return builder;
1061: }
1062: String path = getBuilderPath(builderName, ipath);
1063: if (path != null) {
1064: if (cloudModel != null) {
1065: cloudModel.addBuilder(builderName, path + builderName
1066: + ".xml");
1067: }
1068: return loadBuilderFromXML(builderName, path);
1069: } else {
1070: throw new BuilderConfigurationException(
1071: "Cannot find specified builder " + builderName
1072: + " (" + path + ")");
1073: }
1074: }
1075:
1076: /**
1077: * Create a new builder object using a xml configfile located in a given path relative to the main builder config path,
1078: * and return the builder object.
1079: * If the builder already exists, the existing object is returned instead.
1080: * Note that the builder's init() method is NOT called (since some builders need other builders in memory when their init() is called,
1081: * this method is called seperately after all builders are loaded).
1082: * @deprecation-used uses deprecated builder methods, contains commented-out code
1083: * @param builderName name of the builder to initialize
1084: * @param ipath the path to start searching. The path need be closed with a '/' character.
1085: * @return the loaded builder object.
1086: */
1087: public MMObjectBuilder loadBuilderFromXML(String builderName,
1088: String ipath) {
1089: MMObjectBuilder builder = getMMObject(builderName);
1090: if (builder != null) {
1091: log
1092: .debug("Builder '" + builderName
1093: + "' is already loaded");
1094: return builder;
1095: }
1096:
1097: try {
1098: // register the loading of this builder
1099: loading.add(builderName);
1100: BuilderReader parser = getBuilderReader(ipath + builderName);
1101: if (parser == null) {
1102: loading.remove(builderName);
1103: return null;
1104: }
1105: if (!parser.getRootElement().getTagName().equals("builder")) {
1106: log
1107: .service(ipath
1108: + builderName
1109: + " does not represent a builder xml. Because the root element is not 'builder' but "
1110: + parser.getRootElement().getTagName()
1111: + ". This file is ignored.");
1112: loading.remove(builderName);
1113: return null;
1114: }
1115:
1116: String status = parser.getStatus();
1117: if (status.equals("active")) {
1118: log.service("Starting builder: " + builderName);
1119: Class newclass;
1120: try {
1121: String classname = parser.getClassName();
1122: newclass = Class.forName(classname);
1123: } catch (ClassNotFoundException cnfe) {
1124: MMObjectBuilder p = parser.getParentBuilder();
1125: if (p != null) {
1126: newclass = p.getClass();
1127: } else {
1128: newclass = MMObjectBuilder.class;
1129: }
1130: log.error(cnfe.toString() + " (for '"
1131: + parser.getClassName() + "' of builder '"
1132: + ipath + builderName
1133: + "') Falling back to "
1134: + newclass.getName(), cnfe);
1135: } catch (NoClassDefFoundError ncdfe) {
1136: MMObjectBuilder p = parser.getParentBuilder();
1137: if (p != null) {
1138: newclass = p.getClass();
1139: } else {
1140: newclass = MMObjectBuilder.class;
1141: }
1142: log.error(ncdfe.toString() + " (for '"
1143: + parser.getClassName() + "' of builder '"
1144: + ipath + builderName
1145: + "') Falling back to "
1146: + newclass.getName(), ncdfe);
1147: }
1148: builder = (MMObjectBuilder) newclass.newInstance();
1149:
1150: addBuilder(builderName, builder);
1151:
1152: builder.setXMLPath(ipath);
1153: builder.setMMBase(this );
1154: builder.setTableName(builderName);
1155:
1156: // register the parent builder, if applicable
1157: MMObjectBuilder parent = parser.getParentBuilder();
1158: if (parent != null) {
1159: builder.setParentBuilder(parent);
1160: } else if ((builder instanceof InsRel)
1161: && !builderName.equals("insrel")) {
1162: builder.setParentBuilder(getInsRel());
1163: } else if (!builderName.equals("object")) {
1164: builder.setParentBuilder(getRootBuilder());
1165: }
1166:
1167: Hashtable<String, String> descriptions = parser
1168: .getDescriptions();
1169: builder.setDescriptions(descriptions);
1170: String desc = descriptions.get(locale.getLanguage());
1171: // XXX" set description by builder?
1172: builder.setDescription(desc);
1173: builder.setSingularNames(parser.getSingularNames());
1174: builder.setPluralNames(parser.getPluralNames());
1175: builder.setVersion(parser.getVersion());
1176: builder.setMaintainer(parser.getMaintainer());
1177: builder.setSearchAge("" + parser.getSearchAge());
1178: builder.setInitParameters(parser.getProperties());
1179: parser.getDataTypes(builder.getDataTypeCollector());
1180: builder.setFields(parser.getFields(builder, builder
1181: .getDataTypeCollector()));
1182: builder.getStorageConnector().addIndices(
1183: parser.getIndices(builder));
1184: for (Function func : parser.getFunctions(builder)) {
1185: Function prev = builder.addFunction(func);
1186: log.service((prev == null ? "Added " : "Replaced ")
1187: + func + " to " + builder);
1188: }
1189: if (parent != null) {
1190: for (Function parentFunction : parent
1191: .getFunctions()) {
1192: if (builder.getFunction(parentFunction
1193: .getName()) == null) {
1194: builder.addFunction(parentFunction);
1195: }
1196: }
1197: }
1198: }
1199: } catch (Throwable e) { // what kind of exceptions are these?
1200: loading.remove(builderName);
1201: log.error(e.getClass() + " " + e.getMessage(), e);
1202: return null;
1203: }
1204: loading.remove(builderName);
1205: return builder;
1206: }
1207:
1208: /**
1209: * Loads either the storage manager factory or the appropriate support class using the configuration parameters.
1210: * @since MMBase-1.7
1211: */
1212: protected void initializeStorage() {
1213: if (storageManagerFactory != null)
1214: return; // initialized allready
1215: try {
1216: storageManagerFactory = StorageManagerFactory
1217: .newInstance(this );
1218: // print information about storage
1219: log
1220: .info("Using class: '"
1221: + storageManagerFactory.getClass()
1222: .getName() + "'.");
1223: } catch (StorageException se) {
1224: log.error(se.getMessage());
1225: throw new StorageError();
1226: }
1227: }
1228:
1229: /**
1230: * Returns StorageManagerFactory class used to access the storage configuration.
1231: * @since MMBase-1.7
1232: * @return a StorageManagerFactory class, or <code>null</code> if not configured
1233: */
1234: public StorageManagerFactory getStorageManagerFactory() {
1235: return storageManagerFactory;
1236: }
1237:
1238: /**
1239: * Returns a StorageManager to access the storage.. Equal to getStorageManagerFactory().getStorageManager().
1240: * @since MMBase-1.7
1241: * @return a StorageManager class
1242: * @throws StorageException if no storage manager could be instantiated
1243: */
1244: public StorageManager getStorageManager() throws StorageException {
1245: if (storageManagerFactory == null) {
1246: throw new StorageConfigurationException(
1247: "Storage manager factory not configured.");
1248: } else {
1249: return storageManagerFactory.getStorageManager();
1250: }
1251: }
1252:
1253: /**
1254: * Returns a SearchQueryHandler to access the storage.. Equal to getStorageManagerFactory().getSearchQueryHandler().
1255: * @since MMBase-1.8
1256: * @return a StorageManager class
1257: * @throws StorageException if no storage manager could be instantiated
1258: */
1259: public SearchQueryHandler getSearchQueryHandler()
1260: throws StorageException {
1261: if (storageManagerFactory == null) {
1262: throw new StorageConfigurationException(
1263: "Storage manager factory not configured.");
1264: } else {
1265: return storageManagerFactory.getSearchQueryHandler();
1266: }
1267: }
1268:
1269: /*
1270: * Retrieves the autorisation type.
1271: * This value is set using the configuration file.
1272: * Examples are 'none' or 'basic'.
1273: * @return a <code>String</code> identifying the type
1274: */
1275: public String getAuthType() {
1276: return authtype;
1277: }
1278:
1279: /**
1280: * Retrieves the current language.
1281: * This value is set using the configuration file.
1282: * Examples are 'en' or 'nl'.
1283: * @return the language as a <code>String</code>
1284: */
1285: public String getLanguage() {
1286: return locale.getLanguage();
1287: }
1288:
1289: /**
1290: * Retrieves the current locale.
1291: * @since MMBase-1.8
1292: */
1293: public Locale getLocale() {
1294: return locale;
1295: }
1296:
1297: /**
1298: * Retrieves the timezone asociated with this MMBase's 'DateTime' objects. MMBase stores dates
1299: * in storage as 'Date' but without time-zone information, and therefore to a certain
1300: * degree open to interpretation.
1301: *
1302: * Together with this timezone the times can be defined absoletely (that is, of course, relative
1303: * to the time frame of out planet).
1304: (
1305: * @since MMBase-1.8
1306: */
1307: public TimeZone getTimeZone() {
1308: return timeZone;
1309: }
1310:
1311: /**
1312: * Retrieves the encoding.
1313: * This value is set using the configuration file.
1314: * Examples are 'UTF-8' (default) or 'ISO-8859-1'.
1315: *
1316: * @return the coding as a <code>String</code>
1317: * @since MMBase-1.6
1318: */
1319: public String getEncoding() {
1320: return encoding;
1321: }
1322:
1323: /**
1324: * Retrieves whether this mmbase module is running.
1325: * @return <code>true</code> if the module has been initialized and all builders loaded, <code>false</code> otherwise.
1326: */
1327: public boolean getState() {
1328: return mmbaseState == STATE_UP;
1329: }
1330:
1331: /**
1332: * Checks the builder version and, if needed, updates the version table.
1333: * Queries the xml files instead of the builder itself (?)
1334: * @return Returns <code>true</code> if the builder XML could be read, <code>false</code> otherwise.
1335: */
1336: private boolean checkBuilderVersion(String builderName, Versions ver) {
1337:
1338: MMObjectBuilder tmp = mmobjs.get(builderName);
1339:
1340: if (tmp == null) {
1341: return false;
1342: }
1343:
1344: if (tmp != null) {
1345: int version = tmp.getVersion();
1346: String maintainer = tmp.getMaintainer();
1347:
1348: int installedversion = ver.getInstalledVersion(builderName,
1349: "builder");
1350: if (installedversion == -1 || version > installedversion) {
1351: ver.setInstalledVersion(builderName, "builder",
1352: maintainer, version);
1353: }
1354: }
1355: return true;
1356: }
1357:
1358: /**
1359: * This is a conveniance method to help you register listeners to node and
1360: * relation events. Becouse they are now separate listeners the method accepts
1361: * an object that may have implemented either NodeEvent
1362: * or RelationEvent. This method checks and registers accordingly. <br/>
1363: * the purpose of this method is that a straight node or relation event listeren
1364: * will listen to any node or relation event. This method will wrap your event
1365: * listener to make shure only the requested event types are forwarded.
1366: * @see TypedRelationEventListenerWrapper
1367: * @see TypedNodeEventListenerWrapper
1368: * @see NodeEventListener
1369: * @see RelationEventListener
1370: * @param builder should be a valid builder name, the type for which you want to
1371: * receive events
1372: * @param listener some object implementing NodeEventListener, RelationEventListener,
1373: * or both
1374: * @since MMBase-1.8
1375: */
1376: public void addNodeRelatedEventsListener(String builder,
1377: org.mmbase.core.event.EventListener listener) {
1378: MMObjectBuilder b = getBuilder(builder);
1379: if (b != null) {
1380: if (listener instanceof NodeEventListener) {
1381: TypedNodeEventListenerWrapper tnelr = new TypedNodeEventListenerWrapper(
1382: b, (NodeEventListener) listener, true);
1383: EventManager.getInstance().addEventListener(tnelr);
1384: }
1385: if (listener instanceof RelationEventListener) {
1386: TypedRelationEventListenerWrapper trelr = new TypedRelationEventListenerWrapper(
1387: b, (RelationEventListener) listener,
1388: RelationStep.DIRECTIONS_BOTH, true);
1389: EventManager.getInstance().addEventListener(trelr);
1390: }
1391: }
1392: }
1393:
1394: /**
1395: * @see MMBase#addNodeRelatedEventsListener
1396: * @param builder
1397: * @param listener
1398: * @since MMBase-1.8
1399: */
1400: public void removeNodeRelatedEventsListener(String builder,
1401: org.mmbase.core.event.EventListener listener) {
1402: MMObjectBuilder b = getBuilder(builder);
1403: if (b != null) {
1404: if (listener instanceof NodeEventListener) {
1405: TypedNodeEventListenerWrapper tnelr = new TypedNodeEventListenerWrapper(
1406: b, (NodeEventListener) listener, true);
1407: EventManager.getInstance().removeEventListener(tnelr);
1408: }
1409: if (listener instanceof RelationEventListener) {
1410: TypedRelationEventListenerWrapper trelr = new TypedRelationEventListenerWrapper(
1411: b, (RelationEventListener) listener,
1412: RelationStep.DIRECTIONS_BOTH, true);
1413: EventManager.getInstance().removeEventListener(trelr);
1414:
1415: }
1416: }
1417: }
1418:
1419: }
|