package de.rwth.idsg.bikeman.psinterface.repository;
import de.rwth.idsg.bikeman.domain.CardAccount;
import de.rwth.idsg.bikeman.domain.Pedelec;
import de.rwth.idsg.bikeman.domain.StationSlot;
import de.rwth.idsg.bikeman.domain.Transaction;
import de.rwth.idsg.bikeman.psinterface.dto.request.StartTransactionDTO;
import de.rwth.idsg.bikeman.psinterface.dto.request.StopTransactionDTO;
import de.rwth.idsg.bikeman.psinterface.exception.PsErrorCode;
import de.rwth.idsg.bikeman.psinterface.exception.PsException;
import de.rwth.idsg.bikeman.repository.helper.JpaHelper;
import de.rwth.idsg.bikeman.service.TariffService;
import de.rwth.idsg.bikeman.web.rest.exception.DatabaseException;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.LocalDateTime;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
/**
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
* @since 16.06.2015
*/
@Repository
@Slf4j
public class PsiTransactionRepositoryImpl implements PsiTransactionRepository {
@PersistenceContext private EntityManager em;
@Inject private TariffService tariffService;
@Override
@Transactional(readOnly = true)
public int countOpenTransactions(String cardId) {
final String query = "SELECT COUNT(t) FROM Transaction t " +
"WHERE t.cardAccount.cardId = :cardId AND t.endDateTime IS NULL AND t.toSlot IS NULL";
return em.createQuery(query, Long.class)
.setParameter("cardId", cardId)
.getSingleResult()
.intValue();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Transaction start(StartTransactionDTO dto) throws DatabaseException {
// -------------------------------------------------------------------------
// 1. Start transaction
// -------------------------------------------------------------------------
final String pedelecQuery = "SELECT p FROM Pedelec p WHERE p.manufacturerId = :pedelecManufacturerId";
final String cardAccQuery = "SELECT ca FROM CardAccount ca WHERE ca.cardId = :cardId";
Pedelec pedelec = em.createQuery(pedelecQuery, Pedelec.class)
.setParameter("pedelecManufacturerId", dto.getPedelecManufacturerId())
.getSingleResult();
CardAccount cardAccount = em.createQuery(cardAccQuery, CardAccount.class)
.setParameter("cardId", dto.getCardId())
.getSingleResult();
StationSlot slot = pedelec.getStationSlot();
// Check integrity of station slot
String entitySlotManId = slot.getManufacturerId();
String dtoSlotManId = dto.getSlotManufacturerId();
boolean slotOK = entitySlotManId.equalsIgnoreCase(dtoSlotManId);
if (!slotOK) {
throw new PsException("Integrity check of station slot manufacturerId failed: "
+ dtoSlotManId + " (sent from station) != "
+ entitySlotManId + " (DB value)", PsErrorCode.CONSTRAINT_FAILED);
}
// Check integrity of station
String entityStationManId = slot.getStation().getManufacturerId();
String dtoStationManId = dto.getStationManufacturerId();
boolean stationOK = entityStationManId.equalsIgnoreCase(dtoStationManId);
if (!stationOK) {
throw new PsException("Integrity check of station manufacturerId failed: "
+ dtoStationManId + " (sent from station) != "
+ entityStationManId + " (DB value)", PsErrorCode.CONSTRAINT_FAILED);
}
Transaction transaction = new Transaction();
// add one sec to timestamp, because of rounding error: UNIX timestamp kills millis
LocalDateTime start = dto.getTimestamp().toLocalDateTime().plusSeconds(1);
transaction.setStartDateTime(start);
transaction.setCardAccount(cardAccount);
transaction.setPedelec(pedelec);
transaction.setFromSlot(slot);
transaction.setBookedTariff(cardAccount.getCurrentTariff());
em.persist(transaction);
// -------------------------------------------------------------------------
// 2. Update related entities
// -------------------------------------------------------------------------
cardAccount.setInTransaction(true);
pedelec.setInTransaction(true);
pedelec.setStationSlot(null);
slot.setIsOccupied(false);
slot.setPedelec(null);
em.merge(cardAccount);
em.merge(pedelec);
em.merge(slot);
return transaction;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Transaction stop(StopTransactionDTO dto) throws DatabaseException {
// -------------------------------------------------------------------------
// 1. End transaction
// -------------------------------------------------------------------------
final String endQuery = "SELECT t FROM Transaction t " +
"INNER JOIN t.bookedTariff " +
"WHERE t.pedelec = (SELECT p FROM Pedelec p WHERE p.manufacturerId = :pedelecManufacturerId) " +
"AND t.toSlot IS NULL " +
"AND t.endDateTime IS NULL";
final String slotQuery = "SELECT ss FROM StationSlot ss " +
"WHERE ss.manufacturerId = :slotManufacturerId " +
"AND ss.station = (SELECT s FROM Station s WHERE s.manufacturerId = :stationManufacturerId)";
final String pedelecQuery = "SELECT p FROM Pedelec p WHERE p.manufacturerId = :pedelecManufacturerId";
Transaction transaction = JpaHelper.getSingleResult(em.createQuery(endQuery, Transaction.class)
.setParameter("pedelecManufacturerId", dto.getPedelecManufacturerId()));
StationSlot slot = em.createQuery(slotQuery, StationSlot.class)
.setParameter("slotManufacturerId", dto.getSlotManufacturerId())
.setParameter("stationManufacturerId", dto.getStationManufacturerId())
.getSingleResult();
Pedelec pedelec = em.createQuery(pedelecQuery, Pedelec.class)
.setParameter("pedelecManufacturerId", dto.getPedelecManufacturerId())
.getSingleResult();
if (transaction == null) {
log.error("StopTransaction is missing StartTransaction. Skip Transaction and rearrange pedelec.");
// Since StartTransaction was not received, we did not update the old slot. Do it.
em.createQuery("UPDATE StationSlot ss SET ss.pedelec = NULL, ss.isOccupied = FALSE WHERE ss.pedelec = :pedelec")
.setParameter("pedelec", pedelec)
.executeUpdate();
// Register the pedelec at the new slot
em.createQuery("UPDATE StationSlot ss SET ss.isOccupied = true, ss.pedelec = :pedelec WHERE ss = :stationSlot")
.setParameter("pedelec", pedelec)
.setParameter("stationSlot", slot)
.executeUpdate();
return null;
}
transaction.setEndDateTime(dto.getTimestamp().toLocalDateTime());
transaction.setToSlot(slot);
transaction.setFees(tariffService.calculatePrice(transaction));
Transaction mergedTransaction = em.merge(transaction);
// -------------------------------------------------------------------------
// 2. Update related entities
// -------------------------------------------------------------------------
removePedelecFromOldSlot(pedelec.getStationSlot());
CardAccount cardAccount = transaction.getCardAccount();
// Pedelec pedelec = transaction.getPedelec();
cardAccount.setInTransaction(false);
pedelec.setInTransaction(false);
pedelec.setStationSlot(slot);
slot.setIsOccupied(true);
slot.setPedelec(pedelec);
em.merge(cardAccount);
em.merge(pedelec);
em.merge(slot);
return mergedTransaction;
}
// remove only when necessary
private void removePedelecFromOldSlot(StationSlot oldSlot) {
if (oldSlot == null || oldSlot.getPedelec() == null) {
return;
}
oldSlot.setPedelec(null);
em.merge(oldSlot);
}
}