Package org.xtreemfs.babudb

Source Code of org.xtreemfs.babudb.BabuDBImpl

/*
* Copyright (c) 2009 - 2011, Jan Stender, Bjoern Kolbeck, Mikael Hoegqvist,
*                     Felix Hupfeld, Felix Langner, Zuse Institute Berlin
*
* Licensed under the BSD License, see LICENSE file for details.
*
*/
package org.xtreemfs.babudb;

import static org.xtreemfs.babudb.BabuDBFactory.BABUDB_VERSION;
import static org.xtreemfs.babudb.log.LogEntry.*;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.atomic.AtomicBoolean;

import org.xtreemfs.babudb.api.BabuDB;
import org.xtreemfs.babudb.api.StaticInitialization;
import org.xtreemfs.babudb.api.dev.BabuDBInternal;
import org.xtreemfs.babudb.api.dev.CheckpointerInternal;
import org.xtreemfs.babudb.api.dev.DatabaseInternal;
import org.xtreemfs.babudb.api.dev.DatabaseManagerInternal;
import org.xtreemfs.babudb.api.dev.ResponseManagerInternal;
import org.xtreemfs.babudb.api.dev.SnapshotManagerInternal;
import org.xtreemfs.babudb.api.dev.transaction.InMemoryProcessing;
import org.xtreemfs.babudb.api.dev.transaction.OperationInternal;
import org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal;
import org.xtreemfs.babudb.api.exception.BabuDBException;
import org.xtreemfs.babudb.api.exception.BabuDBException.ErrorCode;
import org.xtreemfs.babudb.config.BabuDBConfig;
import org.xtreemfs.babudb.conversion.AutoConverter;
import org.xtreemfs.babudb.log.DiskLogIterator;
import org.xtreemfs.babudb.log.DiskLogger;
import org.xtreemfs.babudb.log.LogEntry;
import org.xtreemfs.babudb.log.DiskLogger.SyncMode;
import org.xtreemfs.babudb.lsmdb.CheckpointerImpl;
import org.xtreemfs.babudb.lsmdb.DBConfig;
import org.xtreemfs.babudb.lsmdb.DatabaseManagerImpl;
import org.xtreemfs.babudb.lsmdb.LSMDBWorker;
import org.xtreemfs.babudb.lsmdb.LSMDatabase;
import org.xtreemfs.babudb.lsmdb.LSN;
import org.xtreemfs.babudb.snapshots.SnapshotManagerImpl;
import org.xtreemfs.foundation.LifeCycleThread;
import org.xtreemfs.foundation.VersionManagement;
import org.xtreemfs.foundation.logging.Logging;

/**
* BabuDB main class.
*
* <p>
* <b>Please use the {@link BabuDBFactory} to generate instances of
* {@link BabuDB}.</b>
* </p>
*
* @author bjko
* @author flangner
* @author stenjan
*
*/
public class BabuDBImpl implements BabuDBInternal {
   
    private LSMDBWorker[]                 worker;
   
    /**
     * the disk logger is used to write InsertRecordGroups persistently to disk
     */
    private DiskLogger                    logger;
   
    private TransactionManagerInternal    txnMan;
   
    /**
     * Checkpointer thread for automatic checkpointing
     */
    private final CheckpointerInternal    dbCheckptr;
   
    /**
     * the component that manages database snapshots
     */
    private final SnapshotManagerInternal snapshotManager;
   
    /**
     * the component that manages databases
     */
    private final DatabaseManagerInternal databaseManager;
   
    /**
     * the thread managing user-response listeners
     */
    private final ResponseManagerImpl     responseManager;
   
    /**
     * All necessary parameters to run the BabuDB.
     */
    private final BabuDBConfig            configuration;
   
    /**
     * File used to store meta-informations about DBs.
     */
    private final DBConfig                dbConfigFile;
   
    /**
     * Flag that shows the replication if babuDB is running at the moment.
     */
    private final AtomicBoolean           stopped = new AtomicBoolean(true);
   
    /**
     * Threads used within the plugins. They will be stopped when BabuDB shuts
     * down.
     */
    private final List<LifeCycleThread>   plugins = new Vector<LifeCycleThread>();
   
    static {
       
        // BufferPool.enableStackTraceRecording(true);
       
        // Check if the correct version of the foundation JAR is available. If
        // this is not the case, an error is thrown immediately on startup.
        // Whenever a new foundation JAR is imported, the required version
        // number should be adjusted, so as to make sure that BabuDB behaves as
        // expected.
        final int requiredFoundationVersion = 2;
       
        if (VersionManagement.getFoundationVersion() != requiredFoundationVersion) {
            throw new Error("Foundation.jar in classpath is required in version " + requiredFoundationVersion
                + "; present version is " + VersionManagement.getFoundationVersion());
        }
    }
   
    /**
     * Initializes the basic components of the BabuDB database system.
     *
     * @param configuration
     * @throws BabuDBException
     */
    BabuDBImpl(BabuDBConfig configuration) throws BabuDBException {
       
        this.configuration = configuration;
        this.responseManager = new ResponseManagerImpl(configuration.getMaxQueueLength());
        this.txnMan = new TransactionManagerImpl(configuration.getSyncMode().equals(SyncMode.ASYNC));
        this.databaseManager = new DatabaseManagerImpl(this);
        this.dbConfigFile = new DBConfig(this);
        this.snapshotManager = new SnapshotManagerImpl(this);
        this.dbCheckptr = new CheckpointerImpl(this);
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#init(
     * org.xtreemfs.babudb.StaticInitialization)
     */
    @Override
    public void init(StaticInitialization staticInit) throws BabuDBException {
       
        responseManager.setLifeCycleListener(this);
        responseManager.start();
        snapshotManager.init();
       
        // determine the LSN from which to start the log replay
       
        // to be able to recover from crashes during checkpoints, it is
        // necessary to start with the smallest LSN found on disk
        LSN dbLsn = null;
        for (DatabaseInternal db : databaseManager.getDatabaseList()) {
            if (dbLsn == null)
                dbLsn = db.getLSMDB().getOndiskLSN();
            else {
                LSN onDiskLSN = db.getLSMDB().getOndiskLSN();
                if (!(LSMDatabase.NO_DB_LSN.equals(dbLsn) || LSMDatabase.NO_DB_LSN.equals(onDiskLSN)))
                    dbLsn = dbLsn.compareTo(onDiskLSN) < 0 ? dbLsn : onDiskLSN;
            }
        }
        if (dbLsn == null) {
            // empty database
            dbLsn = new LSN(0, 0);
        } else {
            // need next LSN which is onDisk + 1
            dbLsn = new LSN(dbLsn.getViewId() == 0 ? 1 : dbLsn.getViewId(), dbLsn.getSequenceNo() + 1);
        }
       
        Logging.logMessage(Logging.LEVEL_INFO, this, "starting log replay at LSN %s", dbLsn);
        LSN nextLSN = replayLogs(dbLsn);
        if (dbLsn.compareTo(nextLSN) > 0) {
            nextLSN = dbLsn;
        }
        Logging.logMessage(Logging.LEVEL_INFO, this, "log replay done, using LSN: " + nextLSN);
       
        // set up and start the disk logger
        try {
            logger = new DiskLogger(configuration.getDbLogDir(), nextLSN, configuration.getSyncMode(),
                configuration.getPseudoSyncWait(), configuration.getMaxQueueLength()
                    * Math.max(1, configuration.getNumThreads()));
            logger.setLifeCycleListener(this);
            logger.start();
            logger.waitForStartup();
        } catch (Exception ex) {
            throw new BabuDBException(ErrorCode.IO_ERROR, "cannot start database operations logger", ex);
        }
        this.txnMan.init(new LSN(nextLSN.getViewId(), nextLSN.getSequenceNo() - 1L));
        this.txnMan.setLogger(logger);
       
        if (configuration.getNumThreads() > 0) {
            worker = new LSMDBWorker[configuration.getNumThreads()];
            for (int i = 0; i < configuration.getNumThreads(); i++) {
                worker[i] = new LSMDBWorker(this, i, configuration.getMaxQueueLength());
                worker[i].start();
            }
        } else {
            // number of workers is 0 => requests will be responded directly.
            assert (configuration.getNumThreads() == 0);
           
            worker = null;
        }
       
        if (dbConfigFile.isConversionRequired())
            AutoConverter.completeConversion(this);
       
        // initialize and start the checkpointer; this has to be separated from
        // the instantiation because the instance has to be there when the log
        // is replayed
        this.dbCheckptr.init(logger, configuration.getCheckInterval(), configuration.getMaxLogfileSize());
       
        final LSN firstLSN = new LSN(1, 1L);
        if (staticInit != null && nextLSN.equals(firstLSN)) {
            Logging.logMessage(Logging.LEVEL_DEBUG, this, "Running initialization script...");
            staticInit.initialize(databaseManager, snapshotManager);
            Logging.logMessage(Logging.LEVEL_DEBUG, this, "... initialization script finished successfully.");
        } else if (staticInit != null) {
            Logging.logMessage(Logging.LEVEL_INFO, this, "Static initialization was ignored, "
                + "because database is not empty.");
        }
       
        this.stopped.set(false);
       
        Logging.logMessage(Logging.LEVEL_INFO, this, "BabuDB for Java is running " + "(version "
            + BABUDB_VERSION + ")");
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#addPluginThread(
     * org.xtreemfs.foundation.LifeCycleThread)
     */
    @Override
    public void addPluginThread(LifeCycleThread plugin) {
        plugin.setLifeCycleListener(this);
        this.plugins.add(plugin);
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#stop()
     */
    @Override
    public void stop() {
        synchronized (stopped) {
            if (stopped.get())
                return;
           
            if (worker != null)
                for (LSMDBWorker w : worker)
                    w.shutdown();
           
            logger.shutdown();
            try {
                dbCheckptr.suspendCheckpointing();
                logger.waitForShutdown();
                txnMan.setLogger(null);
                if (worker != null)
                    for (LSMDBWorker w : worker)
                        w.waitForShutdown();
               
            } catch (Exception ex) {
                Logging.logMessage(Logging.LEVEL_ERROR, this, "BabuDB could"
                    + " not be stopped, because '%s'.", ex.getMessage());
            }
            stopped.set(true);
            Logging.logMessage(Logging.LEVEL_INFO, this, "BabuDB has been " + "stopped.");
           
        }
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#restart()
     */
    @Override
    public LSN restart() throws BabuDBException {
        synchronized (stopped) {
           
            if (!stopped.get()) {
                throw new BabuDBException(ErrorCode.IO_ERROR, "BabuDB has to" + " be stopped before!");
            }
           
            databaseManager.reset();
           
            // determine the LSN from which to start the log replay
           
            // to be able to recover from crashes during checkpoints, it is
            // necessary to start with the smallest LSN found on disk
            LSN dbLsn = null;
            for (DatabaseInternal db : databaseManager.getDatabaseList()) {
                if (dbLsn == null)
                    dbLsn = db.getLSMDB().getOndiskLSN();
                else {
                    LSN onDiskLSN = db.getLSMDB().getOndiskLSN();
                    if (!(LSMDatabase.NO_DB_LSN.equals(dbLsn) || LSMDatabase.NO_DB_LSN.equals(onDiskLSN))) {
                        dbLsn = dbLsn.compareTo(onDiskLSN) < 0 ? dbLsn : onDiskLSN;
                    }
                }
            }
            if (dbLsn == null) {
                // empty database
                dbLsn = new LSN(0, 0);
            } else {
                // need next LSN which is onDisk + 1
                dbLsn = new LSN(dbLsn.getViewId(), dbLsn.getSequenceNo() + 1);
            }
           
            Logging.logMessage(Logging.LEVEL_INFO, this, "starting log replay");
            LSN nextLSN = replayLogs(dbLsn);
            if (dbLsn.compareTo(nextLSN) > 0)
                nextLSN = dbLsn;
            Logging.logMessage(Logging.LEVEL_INFO, this, "log replay done, " + "using LSN: " + nextLSN);
           
            try {
                logger = new DiskLogger(configuration.getDbLogDir(), nextLSN, configuration.getSyncMode(),
                    configuration.getPseudoSyncWait(), configuration.getMaxQueueLength()
                        * configuration.getNumThreads());
                logger.setLifeCycleListener(this);
                logger.start();
                logger.waitForStartup();
            } catch (Exception ex) {
                throw new BabuDBException(ErrorCode.IO_ERROR,
                    "Cannot start " + "database operations logger!", ex);
            }
            this.txnMan.init(new LSN(nextLSN.getViewId(), nextLSN.getSequenceNo() - 1L));
            this.txnMan.setLogger(logger);
           
            if (configuration.getNumThreads() > 0) {
                worker = new LSMDBWorker[configuration.getNumThreads()];
                for (int i = 0; i < configuration.getNumThreads(); i++) {
                    worker[i] = new LSMDBWorker(this, i, configuration.getMaxQueueLength());
                    worker[i].start();
                }
            } else {
                // number of workers is 0 => requests will be responded
                // directly.
                assert (configuration.getNumThreads() == 0);
               
                worker = null;
            }
           
            // restart the checkpointer
            this.dbCheckptr.init(logger, configuration.getCheckInterval(), configuration.getMaxLogfileSize());
           
            Logging.logMessage(Logging.LEVEL_INFO, this, "BabuDB for Java is " + "running (version "
                + BABUDB_VERSION + ")");
           
            this.stopped.set(false);
            return new LSN(nextLSN.getViewId(), nextLSN.getSequenceNo() - 1L);
        }
    }
   
    @Override
    public void shutdown() throws BabuDBException {
        shutdown(true);
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.BabuDB#shutdown(boolean graceful)
     */
    @Override
    public void shutdown(boolean graceful) throws BabuDBException {
       
        Logging.logMessage(Logging.LEVEL_INFO, this, "shutting down ...");
       
        if (worker != null) {
            for (LSMDBWorker w : worker) {
                w.shutdown(graceful);
            }
        }
       
        // stop the plugin threads
        BabuDBException exc = null;
        for (LifeCycleThread p : plugins) {
            try {
                p.shutdown();
                p.waitForShutdown();
            } catch (Exception e) {
                if (exc == null)
                    exc = new BabuDBException(ErrorCode.BROKEN_PLUGIN, e.getMessage(), e.getCause());
            }
        }
       
        // shut down the logger; this keeps insertions from being completed
        try {
            logger.shutdown(graceful);
        } catch (Exception e) {
            Logging.logError(Logging.LEVEL_DEBUG, this, e);
        }
       
        // complete checkpoint before shutdown
        try {
            dbCheckptr.shutdown();
            dbCheckptr.waitForShutdown();
        } catch (Exception e) {
            Logging.logError(Logging.LEVEL_DEBUG, this, e);
        }
       
        try {
            databaseManager.shutdown();
            snapshotManager.shutdown();
        } catch (Exception e) {
            Logging.logError(Logging.LEVEL_DEBUG, this, e);
        }
       
        if (worker != null) {
            for (LSMDBWorker w : worker) {
                try {
                    w.waitForShutdown();
                } catch (Exception e) {
                    Logging.logError(Logging.LEVEL_DEBUG, this, e);
                }
            }
           
            Logging.logMessage(Logging.LEVEL_DEBUG, this, "%d worker threads shut down " + "successfully",
                worker.length);
        }
       
        try {
            responseManager.shutdown();
        } catch (Exception e) {
            throw new BabuDBException(ErrorCode.INTERRUPTED, e.getMessage(), e);
        }
       
        if (exc != null) {
            throw exc;
        }
       
        Logging.logMessage(Logging.LEVEL_INFO, this, "BabuDB shutdown complete.");
    }
   
    /**
     * NEVER USE THIS EXCEPT FOR UNIT TESTS! Kills the database.
     */
    @SuppressWarnings("deprecation")
    public void __test_killDB_dangerous() {
        try {
            logger.destroy();
            if (worker != null)
                for (LSMDBWorker w : worker)
                    w.stop();
            ((Thread) dbCheckptr).stop();
           
            this.databaseManager.shutdown();
            this.snapshotManager.shutdown();
            this.responseManager.shutdown();
           
        } catch (Exception ex) {
            // we will probably get that when we kill a thread because we do
            // evil stuff here ;-)
        }
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.BabuDB#getCheckpointer()
     */
    @Override
    public CheckpointerInternal getCheckpointer() {
        return dbCheckptr;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see
     * org.xtreemfs.babudb.api.dev.BabuDBInternal#replaceTransactionManager(
     * org.xtreemfs.babudb.api.dev.transaction.TransactionManagerInternal)
     */
    @Override
    public void replaceTransactionManager(TransactionManagerInternal txnMan) {
        this.txnMan = txnMan;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.dev.BabuDBInternal#getTransactionManager()
     */
    @Override
    public TransactionManagerInternal getTransactionManager() {
        return txnMan;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.BabuDB#getDatabaseManager()
     */
    @Override
    public DatabaseManagerInternal getDatabaseManager() {
        return databaseManager;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#getConfig()
     */
    @Override
    public BabuDBConfig getConfig() {
        return configuration;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.BabuDB#getSnapshotManager()
     */
    @Override
    public SnapshotManagerInternal getSnapshotManager() {
        return snapshotManager;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.api.dev.BabuDBInternal#getResponseManager()
     */
    @Override
    public ResponseManagerInternal getResponseManager() {
        return responseManager;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#getDBConfigFile()
     */
    @Override
    public DBConfig getDBConfigFile() {
        return dbConfigFile;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#getWorkerCount()
     */
    @Override
    public int getWorkerCount() {
        if (worker == null)
            return 0;
        return worker.length;
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.babudb.BabuDBInternal#getWorker(int)
     */
    @Override
    public LSMDBWorker getWorker(int dbId) {
        if (worker == null) {
            return null;
        }
        return worker[dbId % worker.length];
    }
   
    /**
     * Replays the database operations log.
     *
     * @param from
     *            - LSN to replay the logs from.
     *
     * @return the LSN to assign to the next operation
     *
     * @throws BabuDBException
     */
    private LSN replayLogs(LSN from) throws BabuDBException {
       
        try {
            File f = new File(configuration.getDbLogDir());
            File[] logFiles = f.listFiles(new FilenameFilter() {
                public boolean accept(File dir, String name) {
                    return name.endsWith(".dbl");
                }
            });
           
            DiskLogIterator it = new DiskLogIterator(logFiles, from);
            LSN nextLSN = null;
           
            // apply log entries to databases ...
            while (it.hasNext()) {
                LogEntry le = null;
                try {
                    le = it.next();
                    byte type = le.getPayloadType();
                   
                    Logging.logMessage(Logging.LEVEL_DEBUG, this,
                        "Reading entry LSN(%s) of type (%d) with %d bytes payload from log.", le.getLSN()
                                .toString(), (int) type, le.getPayload().remaining());
                   
                    // in normal there are only transactions to be replayed
                    if (type == PAYLOAD_TYPE_TRANSACTION) {
                        txnMan.replayTransaction(le);
                       
                        // create, copy and delete are not replayed (this block
                        // is for backward
                        // compatibility)
                    } else if (type != PAYLOAD_TYPE_CREATE && type != PAYLOAD_TYPE_COPY
                        && type != PAYLOAD_TYPE_DELETE) {
                       
                        // get the processing logic for the dedicated logEntry
                        // type
                        InMemoryProcessing processingLogic = txnMan.getProcessingLogic().get(type);
                       
                        // deserialize the arguments retrieved from the logEntry
                        OperationInternal operation = processingLogic.convertToOperation(processingLogic
                                .deserializeRequest(le.getPayload()));
                       
                        // execute the in-memory logic
                        try {
                            processingLogic.process(operation);
                        } catch (BabuDBException be) {
                           
                            // there might be false positives if a snapshot to
                            // delete has already been deleted, a snapshot to
                            // create has already been created, or an insertion
                            // has been applied to a database that has already
                            // been deleted
                           
                            // FIXME: A clean solution needs to be provided that
                            // is capable of distinguishing between a corrupted
                            // database and a "false positive". False positives
                            // may occur because management operations are
                            // immediately applied to the persistent database
                            // state rather than being applied when the log is
                            // replayed.
                            // A clean solution would involve a single immutable
                            // configuration file per database/snapshot, which
                            // resides in the database directory and is written
                            // when the first checkpoint is created. When a
                            // database/snapshot gets deleted, an additional
                            // (empty) file is created that indicates the
                            // deletion. When old log files are removed in
                            // response to a checkpoint, the system disposes of
                            // all directories of databases and snapshots that
                            // were deleted in these log files.
                            if (!(type == PAYLOAD_TYPE_SNAP && (be.getErrorCode() == ErrorCode.SNAP_EXISTS || be
                                    .getErrorCode() == ErrorCode.NO_SUCH_DB))
                                && !(type == PAYLOAD_TYPE_SNAP_DELETE && be.getErrorCode() == ErrorCode.NO_SUCH_SNAPSHOT)
                                && !(type == PAYLOAD_TYPE_INSERT && be.getErrorCode().equals(
                                    ErrorCode.NO_SUCH_DB))) {
                               
                                throw be;
                            }
                        }
                    }
                   
                    // set LSN
                    nextLSN = new LSN(le.getViewId(), le.getLogSequenceNo() + 1L);
                } finally {
                    if (le != null) {
                        le.free();
                    }
                }
               
            }
           
            it.destroy();
           
            if (nextLSN != null) {
                return nextLSN;
            } else {
                return new LSN(1, 1);
            }
           
        } catch (IOException ex) {
           
            throw new BabuDBException(ErrorCode.IO_ERROR, "cannot load database operations log, "
                + "file might be corrupted", ex);
        } catch (Exception ex) {
           
            Logging.logError(Logging.LEVEL_ERROR, this, ex);
            throw new BabuDBException(ErrorCode.IO_ERROR, "corrupted/incomplete log entry in "
                + "database operations log", ex);
        }
    }
   
    /*
     * Life cycle listener used to handle crashes of plugins.
     */

    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.foundation.LifeCycleListener#startupPerformed()
     */
    @Override
    public void startupPerformed() {
        Logging.logMessage(Logging.LEVEL_INFO, this, "has been successfully started.");
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.foundation.LifeCycleListener#shutdownPerformed()
     */
    @Override
    public void shutdownPerformed() {
        Logging.logMessage(Logging.LEVEL_INFO, this, "has been successfully stopped.");
    }
   
    /*
     * (non-Javadoc)
     *
     * @see org.xtreemfs.foundation.LifeCycleListener#crashPerformed(
     * java.lang.Throwable)
     */
    @Override
    public void crashPerformed(Throwable cause) {
        Logging.logError(Logging.LEVEL_ERROR, this, cause);
        try {
            shutdown();
        } catch (BabuDBException e) {
            Logging.logMessage(Logging.LEVEL_WARN, this, "BabuDB could not have been terminated "
                + "gracefully after plugin crash. " + "Because: %s", e.getMessage());
            e.printStackTrace();
        }
    }
}
TOP

Related Classes of org.xtreemfs.babudb.BabuDBImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.