/**
* This file is part of git-as-svn. It is subject to the license terms
* in the LICENSE file found in the top-level directory of this distribution
* and at http://www.gnu.org/licenses/gpl-2.0.html. No part of git-as-svn,
* including this file, may be copied, modified, propagated, or distributed
* except according to the terms contained in the LICENSE file.
*/
package svnserver.repository.locks;
import org.jetbrains.annotations.NotNull;
import org.mapdb.DB;
import org.mapdb.Serializer;
import org.tmatesoft.svn.core.SVNException;
import svnserver.context.LocalContext;
import svnserver.repository.VcsRepository;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
import java.io.Serializable;
import java.util.SortedMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Persistent lock manager.
*
* @author Artem V. Navrotskiy <bozaro@users.noreply.github.com>
*/
public final class PersistentLockFactory implements LockManagerFactory {
@NotNull
private static final Serializer<LockDesc> serializer = new CustomSerializer();
@NotNull
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
@NotNull
private final SortedMap<String, LockDesc> map;
@NotNull
private final DB db;
public PersistentLockFactory(@NotNull LocalContext context) {
this.db = context.getShared().getCacheDB();
this.map = db.createTreeMap("locks:" + context.getName()).valueSerializer(serializer).makeOrGet();
}
@NotNull
@Override
public <T> T wrapLockRead(@NotNull VcsRepository repo, @NotNull LockWorker<T, LockManagerRead> work) throws IOException, SVNException {
return wrapLock(rwLock.readLock(), repo, work);
}
@NotNull
@Override
public <T> T wrapLockWrite(@NotNull VcsRepository repo, @NotNull LockWorker<T, LockManagerWrite> work) throws IOException, SVNException {
final T result = wrapLock(rwLock.writeLock(), repo, work);
db.commit();
return result;
}
@NotNull
private <T> T wrapLock(@NotNull Lock lock, @NotNull VcsRepository repo, @NotNull LockWorker<T, ? super TreeMapLockManager> work) throws IOException, SVNException {
lock.lock();
try {
return work.exec(new TreeMapLockManager(repo, map));
} finally {
lock.unlock();
}
}
public static class CustomSerializer implements Serializer<LockDesc>, Serializable {
private static final byte VERSION = 1;
@Override
public void serialize(@NotNull DataOutput out, @NotNull LockDesc value) throws IOException {
out.writeByte(VERSION);
out.writeUTF(value.getPath());
out.writeUTF(value.getHash());
out.writeUTF(value.getToken());
out.writeUTF(value.getOwner());
if (value.getComment() != null) {
out.writeBoolean(true);
out.writeUTF(value.getComment());
} else {
out.writeBoolean(false);
}
out.writeLong(value.getCreated());
}
@NotNull
@Override
public LockDesc deserialize(@NotNull DataInput in, int available) throws IOException {
byte version = in.readByte();
if (version != VERSION) {
throw new IOException("Unexpected data format");
}
final String path = in.readUTF();
final String hash = in.readUTF();
final String token = in.readUTF();
final String owner = in.readUTF();
final String comment = in.readBoolean() ? in.readUTF() : null;
final long created = in.readLong();
return new LockDesc(path, hash, token, owner, comment, created);
}
@Override
public int fixedSize() {
return -1;
}
}
}