com.linuxbox.util.lockservice.mongodb.MongoLockService.java Source code

Java tutorial

Introduction

Here is the source code for com.linuxbox.util.lockservice.mongodb.MongoLockService.java

Source

/*******************************************************************************
 * Copyright 2013 The Linux Box Corporation.
 *
 * This file is part of Enkive CE (Community Edition).
 *
 * Enkive CE is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of
 * the License, or (at your option) any later version.
 *
 * Enkive CE is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with Enkive CE. If not, see
 * <http://www.gnu.org/licenses/>.
 *******************************************************************************/
package com.linuxbox.util.lockservice.mongodb;

import static com.linuxbox.util.mongodb.MongoDbConstants.CALL_ENSURE_INDEX_ON_INIT;

import java.net.UnknownHostException;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import com.linuxbox.util.dbinfo.mongodb.MongoDbInfo;
import com.linuxbox.util.lockservice.AbstractRetryingLockService;
import com.linuxbox.util.lockservice.LockAcquisitionException;
import com.linuxbox.util.lockservice.LockReleaseException;
import com.linuxbox.util.mongodb.MongoIndexable;
import com.mongodb.BasicDBObject;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.mongodb.MongoException;
import com.mongodb.QueryBuilder;
import com.mongodb.WriteConcern;

public class MongoLockService extends AbstractRetryingLockService implements MongoIndexable {
    public static class LockRequestFailure {
        public String identifier;
        public Date holderTimestamp;
        public String holderNote;

        public LockRequestFailure(String identifier, Date holderTimestamp, String holderNote) {
            this.identifier = identifier;
            this.holderTimestamp = holderTimestamp;
            this.holderNote = holderNote;
        }
    }

    /**
     * If we try to acquire the lock and fail, we'll return information about
     * the existing lock. But that requires a separate query. What if that query
     * says there is no existing lock? The implication is that the lock was
     * released right after our attempt to create it. So try again to acquire
     * that lock. If we fail after this number of times, assume it's hopless and
     * throw an exception.
     */
    private static int LOCK_RETRY_ATTEMPTS = 4;

    public static int MONGO_DUPLICATE_KEY_ERROR_CODE = 11000;
    private static String LOCK_IDENTIFIER_KEY = "identifier";
    private static String LOCK_NOTE_KEY = "note";
    private static String LOCK_TIMESTAMP_KEY = "timestamp";

    private DBCollection lockCollection;

    private Mongo mongo;
    private boolean mongoCreated;

    public MongoLockService(String server, int port, String database, String collection)
            throws UnknownHostException, MongoException {
        this(new Mongo(server, port), database, collection);
        mongoCreated = true;
    }

    public MongoLockService(String database, String collection) throws UnknownHostException, MongoException {
        this(new Mongo(), database, collection);
        mongoCreated = true;
    }

    public MongoLockService(Mongo mongo, String database, String collection) {
        this(mongo, mongo.getDB(database).getCollection(collection));
    }

    public MongoLockService(MongoDbInfo dbInfo) {
        this(dbInfo.getMongo(), dbInfo.getCollection());
    }

    public MongoLockService(Mongo mongo, DBCollection collection) {
        this.mongo = mongo;
        this.lockCollection = collection;

        lockCollection.setWriteConcern(WriteConcern.FSYNC_SAFE);

        // see comments on def'n of CALL_ENSURE_INDEX_ON_INIT to see why it's
        // done conditionally
        if (CALL_ENSURE_INDEX_ON_INIT) {
            // see class com.linuxbox.enkive.MongoDBIndexManager
        }
    }

    public void startup() {
        lockCollection.remove(new BasicDBObject());
    }

    public void shutdown() {
        if (mongoCreated) {
            mongo.close();
        }
    }

    /**
     * Attempts to create the specified lock. If it fails it returns a record
     * describing the existing lock.
     * 
     * @param identifier
     * @param notation
     * @return
     * @throws LockAcquisitionException
     */
    public LockRequestFailure lockWithFailureData(String identifier, String notation)
            throws LockAcquisitionException {
        final DBObject query = QueryBuilder.start(LOCK_IDENTIFIER_KEY).is(identifier).get();

        for (int i = 0; i < LOCK_RETRY_ATTEMPTS; i++) {
            try {
                lock(identifier, notation);
                return null;
            } catch (LockAcquisitionException e) {
                final DBObject existingLockRecord = lockCollection.findOne(query);
                if (existingLockRecord != null) {
                    return new LockRequestFailure(identifier, (Date) existingLockRecord.get(LOCK_TIMESTAMP_KEY),
                            (String) existingLockRecord.get(LOCK_NOTE_KEY));
                }

                // if we could not find the record, we'll loop back up and
                // re-attempt
            }
        }

        throw new LockAcquisitionException(identifier, "failed after " + LOCK_RETRY_ATTEMPTS + " attempts");
    }

    /**
     * Request sole access to a lock. Returns true if sole access is granted,
     * false otherwise.
     * 
     * @param identifier
     * @return
     */
    @Override
    public void lock(String identifier, Object notation) throws LockAcquisitionException {
        try {
            final DBObject controlRecord = BasicDBObjectBuilder.start(LOCK_IDENTIFIER_KEY, identifier)
                    .add(LOCK_TIMESTAMP_KEY, new Date()).add(LOCK_NOTE_KEY, notation).get();
            lockCollection.insert(controlRecord);
            return;
        } catch (MongoException e) {
            // because the index for identifier is unique, trying to create
            // another record for the same file will generate an exception
            // that we catch here
            throw new LockAcquisitionException(identifier, e);
        }
    }

    /**
     * Releases control of the identifier by removing the record. If the record
     * does not exist then throw a ControlReleaseException.
     * 
     * @param identifier
     * @throws LockReleaseException
     */
    public void releaseLock(String identifier) throws LockReleaseException {
        final DBObject identifierQuery = new QueryBuilder().and(LOCK_IDENTIFIER_KEY).is(identifier).get();
        final DBObject lockRecord = lockCollection.findAndRemove(identifierQuery);
        if (lockRecord == null) {
            throw new LockReleaseException(identifier);
        }
    }

    @Override
    public List<DBObject> getIndexInfo() {
        return lockCollection.getIndexInfo();
    }

    @Override
    public List<IndexDescription> getPreferredIndexes() {
        List<IndexDescription> result = new LinkedList<IndexDescription>();

        /*
         * We want the identifier index to be unique, as that's how we
         * atomically detect when someone tries to create an already-existing
         * lock record
         */
        DBObject lockIndex = BasicDBObjectBuilder.start().add(LOCK_IDENTIFIER_KEY, 1).get();
        IndexDescription id1 = new IndexDescription("lockIndex", lockIndex, true);
        result.add(id1);

        return result;
    }

    @Override
    public void ensureIndex(DBObject index, DBObject options) throws MongoException {
        lockCollection.ensureIndex(index, options);
    }

    @Override
    public long getDocumentCount() throws MongoException {
        return lockCollection.count();
    }
}