Package

Source Code of BdbJavaServer

/*
* Copyright 2012 Yahoo! Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.util.List;
import java.util.ArrayList;
import java.util.HashMap;
import java.io.File;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.thrift.*;
import java.util.Properties;
import java.nio.ByteBuffer;
import com.yahoo.mapkeeper.*;
import java.io.FileInputStream;
import com.sleepycat.je.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.thrift.TException;
import org.apache.thrift.TProcessorFactory;
import org.apache.thrift.protocol.TBinaryProtocol;
import org.apache.thrift.protocol.TBinaryProtocol.Factory;
import org.apache.thrift.protocol.TProtocolFactory;
import org.apache.thrift.server.TServer;
import org.apache.thrift.server.THsHaServer;
import org.apache.thrift.transport.TTransport;
import org.apache.thrift.transport.TServerSocket;
import org.apache.thrift.transport.TNonblockingServerSocket;
import org.apache.thrift.transport.TNonblockingServerTransport;
import org.apache.thrift.transport.TServerTransport;
import org.apache.thrift.transport.TFramedTransport;
import org.apache.thrift.transport.TTransportFactory;

class BdbJavaServer implements MapKeeper.Iface {
    private final Environment env;
    private final HashMap<String, Database> db = new HashMap<String, Database>();

    /**
     * Read/write lock to protect db map.
     *
     * This lock serves 2 purposes.
     * 1. Synchronize access to HashMap.
     * 2. Synchronize access to Database. Database handles are free-threaded
     *    and may be used concurrently by multiple threads. However, close()
     *    method requires an exclusive access to the handle. Therefore threads
     *    that need to call close() must acquire write lock, while other
     *    threads can use read lock.
     */
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = this.lock.readLock();
    private final Lock writeLock = this.lock.writeLock();
    private final Logger logger = LoggerFactory.getLogger(BdbJavaServer.class);

    public BdbJavaServer(Properties properties)
    {
        logger.info(properties.toString());
        String home = properties.getProperty("env_dir", "data");
        EnvironmentConfig envConfig = new EnvironmentConfig();
        envConfig.setAllowCreate(true);
        this.env = new Environment(new File(home), envConfig);

        // open existing dbs
        List<String> databases = this.env.getDatabaseNames();
        DatabaseConfig dbConfig = new DatabaseConfig();
        dbConfig.setTransactional(true);
        dbConfig.setAllowCreate(false);
        for (String dbName : databases) {
            this.db.put(dbName, env.openDatabase(null, dbName, dbConfig));
            logger.debug("opened db: " + dbName);
        }
    }

    /**
     * Pings this persistent store.
     *
     * @return Success - if ping was successful.
     *         Error - if ping failed.
     */
    public ResponseCode ping() throws TException
    {
        return ResponseCode.Success;
    }

    /**
     * Add a new map to this persistent store.
     *
     * A map is a container for a collection of records.
     * A record is a string key / string value pair.
     * A key uniquely identifies a record in a database.
     *
     * @param databaseName database name
     * @return Success - on success.
     *         DatabaseExists - database already exists.
     *         Error - on any other errors.
     */
    public ResponseCode addMap(String databaseName) throws TException
    {
        this.writeLock.lock();
        try {
            logger.debug("adding database " + databaseName);
            DatabaseConfig dbConfig = new DatabaseConfig();
            dbConfig.setTransactional(true);
            dbConfig.setAllowCreate(true);
            dbConfig.setExclusiveCreate(true);
            Database database = env.openDatabase(null, databaseName, dbConfig);
            if (this.db.put(databaseName, database) != null) {
                // this cannot happen. it means that the database didn't exist,
                // but there was an entry in the map for the database name.
                logger.error("failed to add database: " + databaseName);
                return ResponseCode.Error;
            }
            return ResponseCode.Success;
        } catch (DatabaseExistsException ex) {
            return ResponseCode.MapExists;
        } catch (Exception ex) {
            logger.error(ex.toString() + " " + ex.getMessage());
            return ResponseCode.Error;
        } finally {
            this.writeLock.unlock();
        }
    }

    /**
     * Drops a database from this persistent store.
     *
     * @param databaseName database name
     * @return Success - on success.
     *         MapNotFound - database doesn't exist.
     *         Error - on any other errors.
     */
    public ResponseCode dropMap(String databaseName) throws TException
    {
        this.writeLock.lock();
        try {
            logger.debug("dropping database " + databaseName);
            Database db = this.db.remove(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            // We must close Database handle before removing it from
            // the environment.
            db.close();
            this.env.removeDatabase(null, databaseName);
            return ResponseCode.Success;
        } finally {
            this.writeLock.unlock();
        }
    }

    /**
     * List databases in this persistent store.
     *
     * @return StringListResponse
     *              responseCode Success - on success.
     *                           Error - on error.
     *              values - list of databases.
     */
    public StringListResponse listMaps() throws TException
    {
        StringListResponse response = new StringListResponse();
        response.values = this.env.getDatabaseNames();
        response.responseCode = ResponseCode.Success;
        return response;
    }

    /**
     * Returns records in a database in lexicographical order.
     *
     * Note that startKey is supposed to be smaller than or equal to the endKey
     * regardress of the scan order. For example, to scan all the records from
     * "apple" to "banana" in descending order, startKey is "apple" and endKey
     * is "banana". If startKey is larger than endKey, scan will succeed and
     * result will be empty.
     *
     * This method will return ScanEnded if the scan was successful and it reached
     * the end of the key range. It'll return Success if it reached maxRecords or
     * maxBytes, but it didn't reach the end of the key range.
     *
     * @param databaseName
     * @param order Ascending or Decending.
     * @param startKey Key to start scan from. If it's empty, scan starts
     *                 from the smallest key in the database.
     * @param startKeyIncluded
     *                 Indicates whether the record that matches startKey is
     *                 included in the response.
     * @param endKey   Key to end scan at. If it's emty scan ends at the largest
     *                 key in the database.
     * @param endKeyIncluded
     *                 Indicates whether the record that matches endKey is
     *                 included in the response.
     * @param maxRecords
     *                 Scan will return at most $maxRecords records.
     * @param maxBytes Advise scan to return at most $maxBytes bytes. This
     *                 method is not required to strictly keep the response
     *                 size less than $maxBytes bytes.
     * @return RecordListResponse
     *             responseCode - Success if the scan was successful
     *                          - ScanEnded if the scan was successful and
     *                                      scan reached the end of the range.
     *                          - MapNotFound database doesn't exist.
     *                          - Error on any other errors
     *             records - list of records.
     */
    public RecordListResponse scan(String databaseName, ScanOrder order,
        ByteBuffer startKey, boolean startKeyIncluded,
        ByteBuffer endKey, boolean endKeyIncluded,
        int maxRecords, int maxBytes) throws TException
    {
        this.readLock.lock();
        RecordListResponse response = new RecordListResponse();
        Cursor cursor = null;
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                response.responseCode = ResponseCode.MapNotFound;
                return response;
            }
            cursor = db.openCursor(null, null);
            if (order == ScanOrder.Ascending) {
                return scanAscending(cursor, startKey, startKeyIncluded, endKey, endKeyIncluded, maxRecords, maxBytes);
            } else {
                return scanDescending(cursor, startKey, startKeyIncluded, endKey, endKeyIncluded, maxRecords, maxBytes);
            }
        } catch (DatabaseException ex) {
            response.responseCode = ResponseCode.Error;
            return response;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            this.readLock.unlock();
        }
    }

    public RecordListResponse scanAscending(Cursor cursor,
        ByteBuffer startKey, boolean startKeyIncluded,
        ByteBuffer endKey, boolean endKeyIncluded,
        int maxRecords, int maxBytes) throws TException  {
        RecordListResponse response = new RecordListResponse();
        try {
            DatabaseEntry key = new DatabaseEntry(startKey.array(), startKey.position(), startKey.remaining());
            DatabaseEntry value = new DatabaseEntry();
            OperationStatus status = cursor.getSearchKeyRange(key, value, null);
            int numBytes = 0;
            while (true) {
                if (status == OperationStatus.NOTFOUND) {
                    response.responseCode = ResponseCode.ScanEnded;
                    break;
                }
                ByteBuffer currentKey = ByteBuffer.wrap(key.getData());
                if (!startKeyIncluded && currentKey.compareTo(startKey) == 0) {
                    status = cursor.getNext(key, value, null);
                    continue;
                }
                if (endKey.remaining() > 0) {
                    if ((endKeyIncluded && currentKey.compareTo(endKey) > 0) ||
                            (!endKeyIncluded && currentKey.compareTo(endKey) >= 0)) {
                        response.responseCode = ResponseCode.ScanEnded;
                        break;
                    }
                }
                response.addToRecords(new Record(currentKey, ByteBuffer.wrap(value.getData())));
                numBytes += key.getData().length + value.getData().length;
                if (response.records.size() == maxRecords || numBytes >= maxBytes) {
                    response.responseCode = ResponseCode.Success;
                    break;
                }
                status = cursor.getNext(key, value, null);
            }
        } catch (DatabaseException ex) {
            response.responseCode = ResponseCode.Error;
        }
        return response;
    }

    public RecordListResponse scanDescending(Cursor cursor,
        ByteBuffer startKey, boolean startKeyIncluded,
        ByteBuffer endKey, boolean endKeyIncluded,
        int maxRecords, int maxBytes) throws TException  {
        RecordListResponse response = new RecordListResponse();
        try {
            DatabaseEntry key = new DatabaseEntry(endKey.array(), endKey.position(), endKey.remaining());
            DatabaseEntry value = new DatabaseEntry();
            OperationStatus status = OperationStatus.SUCCESS;
            if (endKey.remaining() > 0) {
                status = cursor.getSearchKeyRange(key, value, null);
            }
            if (status == OperationStatus.NOTFOUND || endKey.remaining() == 0) {
                status = cursor.getLast(key, value, null);
                logger.debug("getLast returned" + status);
            }

            int numBytes = 0;
            while (true) {
                logger.debug("michi loop");
                if (status == OperationStatus.NOTFOUND) {
                    logger.debug("NOT FOUND!");
                    response.responseCode = ResponseCode.ScanEnded;
                    break;
                }
                ByteBuffer currentKey = ByteBuffer.wrap(key.getData());
                if (endKey.remaining() > 0) {
                    if ((endKeyIncluded && currentKey.compareTo(endKey) > 0) ||
                            (!endKeyIncluded && currentKey.compareTo(endKey) >= 0)) {
                        status = cursor.getPrev(key, value, null);
                        continue;
                    }
                }
                if (startKey.remaining() > 0) {
                    if ((startKeyIncluded && currentKey.compareTo(startKey) < 0) ||
                        (!startKeyIncluded && currentKey.compareTo(startKey) <= 0)) {
                        response.responseCode = ResponseCode.ScanEnded;
                        break;
                    }
                }
                response.addToRecords(new Record(currentKey, ByteBuffer.wrap(value.getData())));
                numBytes += key.getData().length + value.getData().length;
                if (response.records.size() == maxRecords || numBytes >= maxBytes) {
                    response.responseCode = ResponseCode.Success;
                    break;
                }
                logger.debug("value: " + value.toString());
                status = cursor.getPrev(key, value, null);
            }
        } catch (DatabaseException ex) {
            response.responseCode = ResponseCode.Error;
        }
        return response;
    }

    /**
     * Retrieves a record from a database.
     *
     * @param databaseName
     * @param recordKey
     * @return BinaryResponse
     *              responseCode - Success
     *                             MapNotFound database doesn't exist.
     *                             RecordNotFound record doesn't exist.
     *                             Error on any other errors.
     *              records - list of records
     */
    public BinaryResponse get(String databaseName, ByteBuffer recordKey) throws TException
    {
        this.readLock.lock();
        try {
            BinaryResponse response = new BinaryResponse();
            Database db = this.db.get(databaseName);
            if (db == null) {
                response.responseCode = ResponseCode.MapNotFound;
                return response;
            }
            DatabaseEntry value = new DatabaseEntry();
            OperationStatus status = db.get(null,
                    new DatabaseEntry(recordKey.array(), recordKey.position(), recordKey.remaining()),
                    value, LockMode.READ_COMMITTED);
            if (status == OperationStatus.NOTFOUND) {
                response.responseCode = ResponseCode.RecordNotFound;
                return response;
            }
            response.responseCode = ResponseCode.Success;
            response.value = ByteBuffer.wrap(value.getData());
            return response;
        } finally {
            this.readLock.unlock();
        }
    }

    /**
     * Puts a record into a database.
     *
     * @param databaseName
     * @param recordKey
     * @param recordValue
     * @return Success
     *         MapNotFound database doesn't exist.
     *         Error
     */
    public ResponseCode put(String databaseName, ByteBuffer recordKey, ByteBuffer recordValue) throws TException
    {
        this.readLock.lock();
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            OperationStatus status = db.put(null,
                    new DatabaseEntry(recordKey.array(), recordKey.position(), recordKey.remaining()),
                    new DatabaseEntry(recordValue.array(), recordValue.position(), recordValue.remaining()));
            return ResponseCode.Success;
        } finally {
            this.readLock.unlock();
        }
    }


    /**
     * Inserts a record into a database.
     *
     * @param databaseName
     * @param recordKey
     * @param recordValue
     * @return Success
     *          MapNotFound database doesn't exist.
     *          RecordExists
     *          Error
     */
    public ResponseCode insert(String databaseName, ByteBuffer recordKey, ByteBuffer recordValue) throws TException
    {
        this.readLock.lock();
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            OperationStatus status = db.putNoOverwrite(null,
                    new DatabaseEntry(recordKey.array(), recordKey.position(), recordKey.remaining()),
                    new DatabaseEntry(recordValue.array(), recordValue.position(), recordValue.remaining()));
            if (status == OperationStatus.KEYEXIST) {
                return ResponseCode.RecordExists;
            }
            return ResponseCode.Success;
        } finally {
            this.readLock.unlock();
        }
    }

    /**
     * Inserts multiple records into a database.
     *
     * This operation is atomic: either all the records get inserted into a database
     * or none does.
     *
     * @param databaseName
     * @param records list of records to insert
     * @return Success
     *          MapNotFound
     *          RecordExists - if a record already exists in a database
     *          Error
     */
    public ResponseCode insertMany(String databaseName, List<Record> records) throws TException {
        this.readLock.lock();
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            return ResponseCode.Success;
        } finally {
            this.readLock.unlock();
        }
    }
   
    /**
     * Updates a record in a database.
     *
     * @param databaseName
     * @param recordKey
     * @param recordValue
     * @return Success
     *          MapNotFound map doesn't exist.
     *          RecordNotFound
     *          Error
     */
    public ResponseCode update(String databaseName, ByteBuffer recordKey, ByteBuffer recordValue) throws TException
    {
        Transaction txn = null;
        Cursor cursor = null;
        this.readLock.lock();
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            txn = env.beginTransaction(null, null);
            cursor = db.openCursor(txn, null);
            DatabaseEntry key = new DatabaseEntry(recordKey.array(), recordKey.position(), recordKey.remaining());
            DatabaseEntry value = new DatabaseEntry();
            value.setPartial(0, 0, true);
            OperationStatus status = cursor.getSearchKeyRange(key, value, LockMode.RMW);
            if (status == OperationStatus.NOTFOUND) {
                return ResponseCode.RecordNotFound;
            }
            status = cursor.putCurrent(new DatabaseEntry(recordValue.array(), recordValue.position(), recordValue.remaining()));
            if (status != OperationStatus.SUCCESS) {
                return ResponseCode.Error;
            }
            cursor.close();
            txn.commit();
            return ResponseCode.Success;
        } catch (DatabaseException ex) {
            logger.error(ex.getMessage());
            cursor.close();
            txn.abort();
            return ResponseCode.Error;
        } finally {
            if (cursor != null) {
                cursor.close();
            }
            this.readLock.unlock();
        }
    }

    /**
     * Removes a record from a database.
     *
     * @param databaseName
     * @param recordKey
     * @return Success
     *          MapNotFound map doesn't exist.
     *          RecordNotFound
     *          Error
     */
    public ResponseCode remove(String databaseName, ByteBuffer recordKey) throws TException
    {
        this.readLock.lock();
        try {
            Database db = this.db.get(databaseName);
            if (db == null) {
                return ResponseCode.MapNotFound;
            }
            OperationStatus status = db.delete(null, new DatabaseEntry(recordKey.array(), recordKey.position(), recordKey.remaining()));
            if (status == OperationStatus.NOTFOUND) {
                return ResponseCode.RecordNotFound;
            }
            return ResponseCode.Success;
        } finally {
            this.readLock.unlock();
        }
    }

    public static void main(String argv[]) {
        Logger logger = LoggerFactory.getLogger(BdbJavaServer.class);
        try {
            // load config file
            logger.info("Getting ready...");
            Properties prop = new Properties();
            if(argv.length > 0) {
               prop.load(new FileInputStream(argv[0]));
            }
            int port = Integer.parseInt(prop.getProperty("port", "9090"));
            int numThreads = Integer.parseInt(prop.getProperty("num_threads", "32"));

            BdbJavaServer pstore = new BdbJavaServer(prop);
            TNonblockingServerTransport trans = new TNonblockingServerSocket(port);
            THsHaServer.Args args = new THsHaServer.Args(trans);
            args.transportFactory(new TFramedTransport.Factory());
            args.processor(new MapKeeper.Processor(pstore));
            args.workerThreads(numThreads);
            TServer server = new THsHaServer(args);
            logger.info("Starting server...");
            server.serve();
        } catch (Exception x) {
            x.printStackTrace();
            logger.error(x.toString() + " " + x.getMessage());
        }
    }
}
TOP

Related Classes of BdbJavaServer

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.