/* Copyright (c) 2001-2018, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


package org.hsqldb;

import org.hsqldb.HsqlNameManager.HsqlName;
import org.hsqldb.dbinfo.DatabaseInformation;
import org.hsqldb.error.Error;
import org.hsqldb.error.ErrorCode;
import org.hsqldb.lib.FrameworkLogger;
import org.hsqldb.lib.HashMappedList;
import org.hsqldb.lib.HsqlArrayList;
import org.hsqldb.lib.HsqlTimer;
import org.hsqldb.lib.OrderedHashSet;
import org.hsqldb.map.ValuePool;
import org.hsqldb.persist.HsqlDatabaseProperties;
import org.hsqldb.persist.HsqlProperties;
import org.hsqldb.persist.LobManager;
import org.hsqldb.persist.Logger;
import org.hsqldb.persist.PersistentStoreCollectionDatabase;
import org.hsqldb.result.Result;
import org.hsqldb.rights.GranteeManager;
import org.hsqldb.rights.User;
import org.hsqldb.rights.UserManager;
import org.hsqldb.types.Collation;

// incorporates following contributions
// campbell-burnet@users - javadoc comments

/**
 * Database is the root class for HSQL Database Engine database. <p>
 *
 * It holds the data structures that form an HSQLDB database instance.
 *
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @version 2.4.1
 * @since 1.9.0
 */
public class Database {

    int                        databaseID;
    HsqlName                   databaseUniqueName;
    DatabaseType               databaseType;
    private final String       canonicalPath;
    public HsqlProperties      urlProperties;
    private final String       path;
    public Collation           collation;
    public DatabaseInformation dbInfo;

    /** indicates the state of the database */
    private volatile int dbState;
    public Logger        logger;

    /** true means that all tables are readonly. */
    boolean databaseReadOnly;

    /**
     * true means that all CACHED and TEXT tables are readonly.
     *  MEMORY tables are updatable but updates are not persisted.
     */
    private boolean filesReadOnly;

    /** true means filesReadOnly */
    private boolean filesInJar;

    /**
     * Defaults are used in version upgrades, but overridden by
     *  databaseProperties or URL properties for new databases.
     */
    public int                    sqlAvgScale            = 0;
    public boolean                sqlRestrictExec          = false;
    public boolean                sqlCharLiteral         = true;
    public boolean                sqlConcatNulls         = true;
    public boolean                sqlConvertTruncate     = true;
    public boolean                sqlDoubleNaN           = true;
    public boolean                sqlEnforceTypes        = false;
    public boolean                sqlEnforceRefs         = false;
    public boolean                sqlEnforceSize         = true;
    public boolean                sqlEnforceNames        = false;
    public boolean                sqlEnforceTDCD         = true;
    public boolean                sqlEnforceTDCU         = true;
    public boolean                sqlIgnoreCase          = false;
    public boolean                sqlLiveObject          = false;
    public boolean                sqlLongvarIsLob        = false;
    public boolean                sqlNullsFirst          = true;
    public boolean                sqlNullsOrder          = true;
    public boolean                sqlSysIndexNames       = false;
    public boolean                sqlRegularNames        = true;
    public boolean                sqlTranslateTTI        = true;
    public boolean                sqlUniqueNulls         = true;
    public boolean                sqlSyntaxDb2           = false;
    public boolean                sqlSyntaxMss           = false;
    public boolean                sqlSyntaxMys           = false;
    public boolean                sqlSyntaxOra           = false;
    public boolean                sqlSyntaxPgs           = false;
    public int                    recoveryMode           = 0;
    private boolean               isReferentialIntegrity = true;
    public HsqlDatabaseProperties databaseProperties;
    private final boolean         shutdownOnNoConnection;
    int                           resultMaxMemoryRows;

    // schema invariant objects
    public UserManager     userManager;
    public GranteeManager  granteeManager;
    public HsqlNameManager nameManager;

    // session related objects
    public SessionManager     sessionManager;
    public TransactionManager txManager;
    public int defaultIsolationLevel = SessionInterface.TX_READ_COMMITTED;
    public boolean            txConflictRollback = true;

    // schema objects
    public SchemaManager schemaManager;

    //
    public PersistentStoreCollectionDatabase persistentStoreCollection;

    //
    public LobManager lobManager;

    //
    public CheckpointRunner checkpointRunner;
    public TimeoutRunner    timeoutRunner;

    //
    Result updateZeroResult = Result.updateZeroResult;

    //
    public static final int DATABASE_ONLINE       = 1;
    public static final int DATABASE_OPENING      = 2;
    public static final int DATABASE_CLOSING      = 3;
    public static final int DATABASE_SHUTDOWN     = 4;
    public static final int CLOSEMODE_IMMEDIATELY = 1;
    public static final int CLOSEMODE_NORMAL      = 2;
    public static final int CLOSEMODE_COMPACT     = 3;
    public static final int CLOSEMODE_SCRIPT      = 4;

    /**
     *  Constructs a new Database object.
     *
     * @param type is the type of the database: "mem:", "file:", "res:"
     * @param path is the given path to the database files
     * @param canonicalPath is the canonical path
     * @param props property overrides placed on the connect URL
     * @exception  HsqlException if the specified name and path
     *      combination is illegal or unavailable, or the database files the
     *      name and path resolves to are in use by another process
     */
    Database(DatabaseType type, String path, String canonicalPath,
             HsqlProperties props) {

        setState(Database.DATABASE_SHUTDOWN);

        this.databaseType  = type;
        this.path          = path;
        this.canonicalPath = canonicalPath;
        this.urlProperties = props;

        if (databaseType == DatabaseType.DB_RES) {
            filesInJar    = true;
            filesReadOnly = true;
        }

        logger = new Logger(this);
        shutdownOnNoConnection =
            urlProperties.isPropertyTrue(HsqlDatabaseProperties.url_shutdown);
        recoveryMode = urlProperties.getIntegerProperty(
            HsqlDatabaseProperties.url_recover, 0);
    }

    /**
     * Opens this database.  The database should be opened after construction.
     */
    synchronized void open() {

        if (!isShutdown()) {
            return;
        }

        reopen();
    }

    /**
     * Opens this database.  The database should be opened after construction.
     * or reopened by the close(int closemode) method during a
     * "shutdown compact". Closes the log if there is an error.
     */
    void reopen() {

        boolean isNew = false;

        setState(DATABASE_OPENING);

        try {
            createObjectStructures();

            // completed metadata
            logger.open();

            isNew = logger.isNewDatabase;

            if (isNew) {
                String username = urlProperties.getProperty("user", "SA");
                String password = urlProperties.getProperty("password", "");

                userManager.createFirstUser(username, password);
                schemaManager.createPublicSchema();
                logger.checkpoint(null, false, false);
            }

            lobManager.open();
            dbInfo.setWithContent(true);

            checkpointRunner = new CheckpointRunner();
            timeoutRunner    = new TimeoutRunner();
        } catch (Throwable e) {
            logger.close(Database.CLOSEMODE_IMMEDIATELY);
            logger.releaseLock();
            setState(DATABASE_SHUTDOWN);
            clearStructures();
            DatabaseManager.removeDatabase(this);

            if (!(e instanceof HsqlException)) {
                e = Error.error(ErrorCode.GENERAL_ERROR, e);
            }

            logger.logSevereEvent("could not reopen database", e);

            throw (HsqlException) e;
        }

        setState(DATABASE_ONLINE);
    }

    /**
     * Clears the data structures, making them elligible for garbage collection.
     */
    void clearStructures() {

        if (schemaManager != null) {
            schemaManager.release();
        }

        if (checkpointRunner != null) {
            checkpointRunner.stop();
        }

        if (timeoutRunner != null) {
            timeoutRunner.stop();
        }

        lobManager       = null;
        granteeManager   = null;
        userManager      = null;
        nameManager      = null;
        schemaManager    = null;
        sessionManager   = null;
        dbInfo           = null;
        checkpointRunner = null;
        timeoutRunner    = null;
    }

    public void createObjectStructures() {

        nameManager = new HsqlNameManager(this);
        databaseUniqueName = nameManager.newHsqlName("", false,
                SchemaObject.DATABASE);
        lobManager     = new LobManager(this);
        granteeManager = new GranteeManager(this);
        userManager    = new UserManager(this);
        schemaManager  = new SchemaManager(this);
        persistentStoreCollection =
            new PersistentStoreCollectionDatabase(this);
        isReferentialIntegrity = true;
        sessionManager         = new SessionManager(this);
        collation              = Collation.newDatabaseInstance();
        dbInfo = DatabaseInformation.newDatabaseInformation(this);
        txManager              = new TransactionManager2PL(this);

        lobManager.createSchema();
        sessionManager.getSysLobSession().setSchema(SqlInvariants.LOBS_SCHEMA);
        schemaManager.setSchemaChangeTimestamp();
        schemaManager.createSystemTables();
    }

    /**
     *  Returns the database ID.
     */
    public int getDatabaseID() {
        return this.databaseID;
    }

    public HsqlName getName() {
        return databaseUniqueName;
    }

    /**
     * Returns a unique String identifier for the database.
     */
    public String getNameString() {
        return databaseUniqueName.name;
    }

    public void setDatabaseName(String name) {
        databaseUniqueName.rename(name, false);
    }

    /**
     *  Returns the type of the database: "mem", "file", "res"
     */
    public DatabaseType getType() {
        return databaseType;
    }

    /**
     *  Returns the path of the database
     */
    public String getPath() {
        return path;
    }

    public HsqlName getCatalogName() {
        return nameManager.getCatalogName();
    }

    /**
     *  Returns the database properties.
     */
    public HsqlDatabaseProperties getProperties() {
        return databaseProperties;
    }

    /**
     * Returns the SessionManager for the database.
     */
    public SessionManager getSessionManager() {
        return sessionManager;
    }

    public boolean isReadOnly() {
        return databaseReadOnly;
    }

    /**
     *  Returns true if database has been shut down, false otherwise
     */
    boolean isShutdown() {
        return dbState == DATABASE_SHUTDOWN;
    }

    /**
     *  Constructs a new Session that operates within (is connected to) the
     *  context of this Database object. <p>
     *
     *  If successful, the new Session object initially operates on behalf of
     *  the user specified by the supplied user name.
     *
     * Throws if username or password is invalid.
     */
    synchronized Session connect(String username, String password,
                                 String zoneString, int timeZoneSeconds) {

        if (getState() != DATABASE_ONLINE) {
            throw Error.error(ErrorCode.X_08001);
        }

        if (username.equalsIgnoreCase("SA")) {
            username = "SA";
        }

        User user = userManager.getUser(username, password);
        Session session = sessionManager.newSession(this, user,
            databaseReadOnly, true, zoneString, timeZoneSeconds);

        return session;
    }

    /**
     *  Puts this Database object in global read-only mode. After
     *  this call, all existing and future sessions are limited to read-only
     *  transactions. Any following attempts to update the state of the
     *  database will result in throwing an HsqlException.
     */
    public void setReadOnly() {
        databaseReadOnly = true;
        filesReadOnly    = true;
    }

    /**
     * After this call all CACHED and TEXT tables will be set to read-only
     * mode. Changes to MEMORY tables will NOT
     * be stored or updated in the script file. This mode is intended for
     * use with read-only media where data should not be persisted.
     */
    public void setFilesReadOnly() {
        filesReadOnly = true;
    }

    /**
     * Is this in filesReadOnly mode?
     */
    public boolean isFilesReadOnly() {
        return filesReadOnly;
    }

    /**
     * Is this in filesInJar mode?
     */
    public boolean isFilesInJar() {
        return filesInJar;
    }

    /**
     *  Returns the UserManager for this Database.
     */
    public UserManager getUserManager() {
        return userManager;
    }

    /**
     *  Returns the GranteeManager for this Database.
     */
    public GranteeManager getGranteeManager() {
        return granteeManager;
    }

    public void setLiveObject(boolean mode) {
        sqlLiveObject = mode;
    }

    /**
     *  Sets the isReferentialIntegrity attribute.
     */
    public void setReferentialIntegrity(boolean ref) {
        isReferentialIntegrity = ref;
    }

    /**
     *  Is referential integrity currently enforced?
     */
    public boolean isReferentialIntegrity() {
        return isReferentialIntegrity;
    }

    public int getResultMaxMemoryRows() {
        return resultMaxMemoryRows;
    }

    public void setResultMaxMemoryRows(int size) {
        resultMaxMemoryRows = size;
    }

    public void setRestrictExec(boolean mode) {
        sqlRestrictExec = mode;
    }

    public void setStrictNames(boolean mode) {
        sqlEnforceNames = mode;
    }

    public void setRegularNames(boolean mode) {

        sqlRegularNames = mode;

        nameManager.setSqlRegularNames(mode);
    }

    public void setStrictColumnSize(boolean mode) {
        sqlEnforceSize = mode;
    }

    public void setStrictReferences(boolean mode) {
        sqlEnforceRefs = mode;
    }

    public void setStrictTypes(boolean mode) {
        sqlEnforceTypes = mode;
    }

    public void setStrictTDCD(boolean mode) {
        sqlEnforceTDCD = mode;
    }

    public void setStrictTDCU(boolean mode) {
        sqlEnforceTDCU = mode;
    }

    public void setTranslateTTI(boolean mode) {
        sqlTranslateTTI = mode;
    }

    public void setNullsFirst(boolean mode) {
        sqlNullsFirst = mode;
    }

    public void setNullsOrder(boolean mode) {
        sqlNullsOrder = mode;
    }

    public void setCharacterLiteral(boolean mode) {
        sqlCharLiteral = mode;
    }

    public void setConcatNulls(boolean mode) {
        sqlConcatNulls = mode;
    }

    public void setUniqueNulls(boolean mode) {
        sqlUniqueNulls = mode;
    }

    public void setConvertTrunc(boolean mode) {
        sqlConvertTruncate = mode;
    }

    public void setDoubleNaN(boolean mode) {
        sqlDoubleNaN = mode;
    }

    public void setAvgScale(int scale) {
        sqlAvgScale = scale;
    }

    public void setLongVarIsLob(boolean mode) {
        sqlLongvarIsLob = mode;
    }

    public void setIgnoreCase(boolean mode) {
        sqlIgnoreCase = mode;
    }

    public void setSysIndexNames(boolean mode) {
        sqlSysIndexNames = mode;
    }

    public void setSyntaxDb2(boolean mode) {
        sqlSyntaxDb2 = mode;
    }

    public void setSyntaxMss(boolean mode) {
        sqlSyntaxMss = mode;
    }

    public void setSyntaxMys(boolean mode) {
        sqlSyntaxMys = mode;
    }

    public void setSyntaxOra(boolean mode) {
        sqlSyntaxOra = mode;
    }

    public void setSyntaxPgs(boolean mode) {
        sqlSyntaxPgs = mode;
    }

    /**
     *  Called by the garbage collector on this Databases object when garbage
     *  collection determines that there are no more references to it.
     */
    protected void finalize() {

        if (getState() != DATABASE_ONLINE) {
            return;
        }

        try {
            close(CLOSEMODE_IMMEDIATELY);
        } catch (HsqlException e) {    // it's too late now
        }
    }

    void closeIfLast() {

        if (sessionManager.isEmpty() && dbState == DATABASE_ONLINE) {
            if (shutdownOnNoConnection) {
                try {
                    close(CLOSEMODE_NORMAL);
                } catch (HsqlException e) {}
            } else {
                logger.synchLog();
            }
        }
    }

    /**
     *  Closes this Database using the specified mode. <p>
     *
     * <ol>
     *  <LI> closemode -1 performs SHUTDOWN IMMEDIATELY, equivalent
     *       to a poweroff or crash.
     *
     *  <LI> closemode 0 performs a normal SHUTDOWN that
     *      checkpoints the database normally.
     *
     *  <LI> closemode 1 performs a shutdown compact that scripts
     *       out the contents of any CACHED tables to the log then
     *       deletes the existing *.data file that contains the data
     *       for all CACHED table before the normal checkpoint process
     *       which in turn creates a new, compact *.data file.
     * </ol>
     */
    public void close(int closemode) {

        HsqlException he = null;

        // multiple simultaneous close
        synchronized (this) {
            if (getState() != DATABASE_ONLINE) {
                return;
            }

            setState(DATABASE_CLOSING);
        }

        sessionManager.closeAllSessions();

        if (filesReadOnly) {
            closemode = CLOSEMODE_IMMEDIATELY;
        }

        /**
         * impact of possible error conditions in closing the log
         * for the CLOSEMODE_COMPACT mode
         */
        boolean result = logger.close(closemode);

        lobManager.close();
        sessionManager.close();

        try {
            if (result && closemode == CLOSEMODE_COMPACT) {
                clearStructures();
                reopen();
                txManager.setGlobalChangeTimestamp(
                    txManager.getGlobalChangeTimestamp() + 1);
                setState(DATABASE_CLOSING);
                sessionManager.closeAllSessions();
                logger.close(CLOSEMODE_NORMAL);
                lobManager.close();
                sessionManager.close();
            }
        } catch (Throwable t) {
            if (t instanceof HsqlException) {
                he = (HsqlException) t;
            } else {
                he = Error.error(ErrorCode.GENERAL_ERROR, t);
            }
        }

        logger.releaseLock();
        setState(DATABASE_SHUTDOWN);
        clearStructures();

        // fredt - this could change to avoid removing a db from the
        // DatabaseManager repository if there are pending getDatabase()
        // calls
        DatabaseManager.removeDatabase(this);

        // todo - when hsqldb.sql. framework logging is supported, add another call
        FrameworkLogger.clearLoggers("hsqldb.db." + getNameString());

        if (he != null) {
            throw he;
        }
    }

    private void setState(int state) {
        dbState = state;
    }

    int getState() {
        return dbState;
    }

    String getStateString() {

        int state = getState();

        switch (state) {

            case DATABASE_CLOSING :
                return "DATABASE_CLOSING";

            case DATABASE_ONLINE :
                return "DATABASE_ONLINE";

            case DATABASE_OPENING :
                return "DATABASE_OPENING";

            case DATABASE_SHUTDOWN :
                return "DATABASE_SHUTDOWN";

            default :
                return "UNKNOWN";
        }
    }

    public String[] getSettingsSQL() {

        HsqlArrayList list = new HsqlArrayList();
        StringBuffer  sb   = new StringBuffer();

        if (!getCatalogName().name.equals(
                SqlInvariants.DEFAULT_CATALOG_NAME)) {
            String name = getCatalogName().statementName;

            sb.append("ALTER CATALOG PUBLIC RENAME TO ").append(name);
            list.add(sb.toString());
            sb.setLength(0);
        }

        if (!collation.isDefaultCollation()) {
            list.add(collation.getDatabaseCollationSQL());
        }

        HashMappedList lobTables =
            schemaManager.getTables(SqlInvariants.LOBS_SCHEMA);

        for (int i = 0; i < lobTables.size(); i++) {
            Table table = (Table) lobTables.get(i);

            if (table.isCached()) {
                sb.append(Tokens.T_SET).append(' ').append(Tokens.T_TABLE);
                sb.append(' ');
                sb.append(table.getName().getSchemaQualifiedStatementName());
                sb.append(' ').append(Tokens.T_TYPE).append(' ');
                sb.append(Tokens.T_CACHED);
                list.add(sb.toString());
                sb.setLength(0);
            }
        }

        String[] array = new String[list.size()];

        list.toArray(array);

        return array;
    }

    /**
     * Returns the schema and authorisation statements for the database.
     */
    public Result getScript(boolean indexRoots) {

        Result r = Result.newSingleColumnResult("COMMAND");

        // properties
        String[] list = logger.getPropertiesSQL(indexRoots);

        r.addRows(list);

        list = getSettingsSQL();

        r.addRows(list);

        list = granteeManager.getSQL();

        r.addRows(list);

        // schemas and schema objects such as tables, sequences, etc.
        list = schemaManager.getSQLArray();

        r.addRows(list);

        // optional comments on tables etc.
        list = schemaManager.getCommentsSQL();

        r.addRows(list);

        list = schemaManager.getTableSpaceSQL();

        r.addRows(list);

        // index roots
        if (indexRoots) {
            list = schemaManager.getIndexRootsSQL();

            r.addRows(list);
        }

        // text headers - readonly - clustered
        list = schemaManager.getTablePropsSQL(!indexRoots);

        r.addRows(list);

        // password complexity
        list = userManager.getAuthenticationSQL();

        r.addRows(list);

        // user session start schema names
        list = userManager.getInitialSchemaSQL();

        r.addRows(list);

        // grantee rights
        list = granteeManager.getRightsSQL();

        r.addRows(list);

        return r;
    }

    public String getURI() {
        return databaseType.value() + canonicalPath;
    }

    public String getCanonicalPath() {
        return canonicalPath;
    }

    public HsqlProperties getURLProperties() {
        return urlProperties;
    }

    public TimeoutRunner getTimeoutRunner() {
        return timeoutRunner;
    }

    class CheckpointRunner implements Runnable {

        private volatile boolean waiting;
        private Object           timerTask;

        public void run() {

            Statement checkpoint =
                ParserCommand.getAutoCheckpointStatement(Database.this);
            Session sysSession = sessionManager.newSysSession();

            try {
                sysSession.executeCompiledStatement(checkpoint,
                                                    ValuePool.emptyObjectArray,
                                                    0);
            } catch (Throwable e) {

                // ignore exceptions
                // may be InterruptedException or IOException
            } finally {
                sysSession.commit(false);
                sysSession.close();

                waiting = false;
            }
        }

        public void start() {

            if (!logger.isLogged()) {
                return;
            }

            synchronized (this) {
                if (waiting) {
                    return;
                }

                waiting = true;
            }

            timerTask = DatabaseManager.getTimer().scheduleAfter(0, this);
        }

        public void stop() {

            HsqlTimer.cancel(timerTask);

            timerTask = null;
            waiting   = false;
        }
    }

    static class TimeoutRunner implements Runnable {

        private Object timerTask;
        OrderedHashSet sessionList;

        public void run() {

            try {
                for (int i = sessionList.size() - 1; i >= 0; i--) {
                    Session session = (Session) sessionList.get(i);

                    if (session.isClosed()) {
                        synchronized (this) {
                            sessionList.remove(i);
                        }

                        continue;
                    }

                    boolean result = session.timeoutManager.checkTimeout();

                    if (result) {
                        synchronized (this) {
                            sessionList.remove(i);
                        }
                    }
                }
            } catch (Throwable e) {

                // ignore exceptions
                // may be InterruptedException or IOException
            }
        }

        public void start() {

            sessionList = new OrderedHashSet();
            timerTask = DatabaseManager.getTimer().schedulePeriodicallyAfter(0,
                    1000, this, true);
        }

        public void stop() {

            synchronized (this) {
                if (timerTask == null) {
                    return;
                }

                HsqlTimer.cancel(timerTask);
                sessionList.clear();

                timerTask   = null;
                sessionList = null;
            }
        }

        public void addSession(Session session) {

            synchronized (this) {
                if (timerTask == null) {
                    start();
                }

                sessionList.add(session);
            }
        }
    }
}
