/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.
*/
package org.keycloak.connections.mongo.lock;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.DuplicateKeyException;
import com.mongodb.WriteResult;
import org.jboss.logging.Logger;
import org.keycloak.common.util.HostUtils;
import org.keycloak.common.util.Time;
import org.keycloak.models.dblock.DBLockProvider;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class MongoDBLockProvider implements DBLockProvider {
private static final String DB_LOCK_COLLECTION = "dblock";
private static final Logger logger = Logger.getLogger(MongoDBLockProvider .class);
private final MongoDBLockProviderFactory factory;
private final DB db;
public MongoDBLockProvider(MongoDBLockProviderFactory factory, DB db) {
this.factory = factory;
this.db = db;
}
@Override
public void waitForLock() {
boolean locked = false;
long startTime = Time.toMillis(Time.currentTime());
long timeToGiveUp = startTime + (factory.getLockWaitTimeoutMillis());
while (!locked && Time.toMillis(Time.currentTime()) < timeToGiveUp) {
locked = acquireLock();
if (!locked) {
int remainingTime = ((int)(timeToGiveUp / 1000)) - Time.currentTime();
logger.debugf("Waiting for changelog lock... Remaining time: %d seconds", remainingTime);
try {
Thread.sleep(factory.getLockRecheckTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (!locked) {
DBObject query = new BasicDBObject("_id", 1);
DBCursor cursor = db.getCollection(DB_LOCK_COLLECTION).find(query);
String lockedBy;
if (cursor.hasNext()) {
DBObject dbObj = cursor.next();
lockedBy = dbObj.get("lockedBy") + " since " + Time.toDate(((int)((long) dbObj.get("lockedSince") / 1000)));
} else {
lockedBy = "UNKNOWN";
}
throw new IllegalStateException("Could not acquire change log lock. Currently locked by " + lockedBy);
}
}
private boolean acquireLock() {
DBObject query = new BasicDBObject("locked", false);
BasicDBObject update = new BasicDBObject("locked", true);
update.append("_id", 1);
update.append("lockedSince", Time.toMillis(Time.currentTime()));
update.append("lockedBy", HostUtils.getHostName()); // Maybe replace with something better, but doesn't matter for now
try {
WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
if (wr.getN() == 1) {
logger.debugf("Successfully acquired DB lock");
factory.setHasLock(true);
return true;
} else {
return false;
}
} catch (DuplicateKeyException dke) {
logger.debugf("Failed acquire lock. Reason: %s", dke.getMessage());
}
return false;
}
@Override
public void releaseLock() {
DBObject query = new BasicDBObject("locked", true);
BasicDBObject update = new BasicDBObject("locked", false);
update.append("_id", 1);
update.append("lockedBy", null);
update.append("lockedSince", null);
try {
WriteResult wr = db.getCollection(DB_LOCK_COLLECTION).update(query, update, true, false);
if (wr.getN() > 0) {
factory.setHasLock(false);
logger.debugf("Successfully released DB lock");
} else {
logger.warnf("Attempt to release DB lock, but nothing was released");
}
} catch (DuplicateKeyException dke) {
logger.debugf("Failed release lock. Reason: %s", dke.getMessage());
}
}
@Override
public boolean hasLock() {
return factory.hasLock();
}
@Override
public boolean supportsForcedUnlock() {
return true;
}
@Override
public void destroyLockInfo() {
db.getCollection(DB_LOCK_COLLECTION).remove(new BasicDBObject());
logger.debugf("Destroyed lock collection");
}
@Override
public void close() {
}
}