fi.hsl.parkandride.back.LockDao.java Source code

Java tutorial

Introduction

Here is the source code for fi.hsl.parkandride.back.LockDao.java

Source

// Copyright  2015 HSL <https://www.hsl.fi>
// This program is dual-licensed under the EUPL v1.2 and AGPLv3 licenses.

package fi.hsl.parkandride.back;

import com.querydsl.core.QueryException;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.MappingProjection;
import com.querydsl.sql.postgresql.PostgreSQLQueryFactory;
import fi.hsl.parkandride.back.sql.QLock;
import fi.hsl.parkandride.core.back.LockRepository;
import fi.hsl.parkandride.core.domain.Lock;
import fi.hsl.parkandride.core.domain.LockAcquireFailedException;
import fi.hsl.parkandride.core.domain.LockException;
import fi.hsl.parkandride.core.service.ValidationService;
import org.joda.time.DateTime;
import org.joda.time.Duration;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

public class LockDao implements LockRepository {

    private static final QLock qLock = QLock.lock;

    private static final MappingProjection<Lock> lockMapping = new MappingProjection<Lock>(Lock.class,
            qLock.all()) {
        @Override
        protected Lock map(Tuple row) {
            return new Lock(row.get(qLock.name), row.get(qLock.owner), row.get(qLock.validUntil));
        }
    };

    private final PostgreSQLQueryFactory queryFactory;
    private final ValidationService validationService;
    private final String ownerName;

    public LockDao(PostgreSQLQueryFactory queryFactory, ValidationService validationService, String lockOwnerName) {
        this.queryFactory = queryFactory;
        this.validationService = validationService;
        this.ownerName = lockOwnerName;
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
    public Lock acquireLock(String lockName, Duration lockDuration) {
        Optional<Lock> lock = selectLockIfExists(lockName);
        if (lock.isPresent()) {
            Lock existingLock = lock.get();
            if (!existingLock.validUntil.isAfter(DateTime.now())) {
                return claimExpiredLock(existingLock, lockDuration);
            } else {
                throw new LockAcquireFailedException("Existing lock " + existingLock + " is still valid");
            }
        }
        return insertLock(lockName, lockDuration);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.SERIALIZABLE)
    public boolean releaseLock(Lock lock) {
        validationService.validate(lock);
        if (ownerName.equals(lock.owner)) {
            return deleteLock(lock) == 1;
        } else {
            throw new LockException("Cannot release lock. Lock is not owned by this node.");
        }
    }

    // Following mehods are protected to allow testing

    protected Optional<Lock> selectLockIfExists(String lockName) {
        return Optional
                .ofNullable(queryFactory.from(qLock).where(qLock.name.eq(lockName)).select(lockMapping).fetchOne());
    }

    protected Lock claimExpiredLock(Lock existingLock, Duration lockDuration) {
        try {
            final DateTime newValidUntil = DateTime.now().plus(lockDuration);
            long rowsUpdated = queryFactory.update(qLock).where(qLock.name.eq(existingLock.name))
                    .where(qLock.owner.eq(existingLock.owner)).where(qLock.validUntil.eq(existingLock.validUntil))
                    .set(qLock.owner, ownerName).set(qLock.validUntil, newValidUntil).execute();
            if (rowsUpdated > 0) {
                return new Lock(existingLock.name, ownerName, newValidUntil);
            } else {
                throw getFailedToClaimExpiredLockException(existingLock, null);
            }
        } catch (QueryException e) {
            throw getFailedToClaimExpiredLockException(existingLock, e);
        }
    }

    private LockAcquireFailedException getFailedToClaimExpiredLockException(Lock existingLock, QueryException e) {
        return new LockAcquireFailedException("Failed to claim expired lock " + existingLock + " for " + ownerName,
                e);
    }

    protected Lock insertLock(String lockName, Duration lockDuration) {
        final DateTime validUntil = DateTime.now().plus(lockDuration);
        final Lock newLock = new Lock(lockName, ownerName, validUntil);
        try {
            long rowsInserted = queryFactory.insert(qLock).columns(qLock.name, qLock.owner, qLock.validUntil)
                    .values(lockName, ownerName, validUntil).execute();
            if (rowsInserted > 0) {
                return newLock;
            } else {
                throw getInsertLockFailedException(lockName, null);
            }
        } catch (QueryException e) {
            throw getInsertLockFailedException(lockName, e);
        }
    }

    private LockAcquireFailedException getInsertLockFailedException(String lockName, QueryException e) {
        return new LockAcquireFailedException(
                "Failed to acquire lock '" + lockName + "' (lost acquisition race to another node)", e);
    }

    protected long deleteLock(Lock lock) {
        return queryFactory.delete(qLock).where(qLock.name.eq(lock.name)).where(qLock.owner.eq(lock.owner))
                .where(qLock.validUntil.eq(lock.validUntil)).execute();
    }
}