package de.rwth.idsg.bikeman.psinterface.repository;
import com.google.common.base.Strings;
import de.rwth.idsg.bikeman.domain.OperationState;
import de.rwth.idsg.bikeman.domain.Pedelec;
import de.rwth.idsg.bikeman.domain.Station;
import de.rwth.idsg.bikeman.domain.StationSlot;
import de.rwth.idsg.bikeman.psinterface.dto.request.BootNotificationDTO;
import de.rwth.idsg.bikeman.psinterface.dto.request.SlotDTO;
import de.rwth.idsg.bikeman.psinterface.dto.request.StationStatusDTO;
import de.rwth.idsg.bikeman.psinterface.dto.response.CardReadKeyDTO;
import de.rwth.idsg.bikeman.psinterface.dto.response.CardWriteKeyDTO;
import de.rwth.idsg.bikeman.psinterface.exception.PsErrorCode;
import de.rwth.idsg.bikeman.psinterface.exception.PsException;
import de.rwth.idsg.bikeman.repository.PedelecRepository;
import de.rwth.idsg.bikeman.utils.ItemIdComparator;
import de.rwth.idsg.bikeman.web.rest.exception.DatabaseException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Sevket Goekay <goekay@dbis.rwth-aachen.de>
* @since 16.06.2015
*/
@Repository
@Slf4j
public class PsiStationRepositoryImpl implements PsiStationRepository {
@PersistenceContext private EntityManager em;
@Inject private PedelecRepository pedelecRepository;
@Override
public List<CardReadKeyDTO> getCardReadKeys() {
final String q = "SELECT new de.rwth.idsg.bikeman.psinterface.dto.response." +
"CardReadKeyDTO(c.name, c.readKey) " +
"FROM CardKey c";
return em.createQuery(q, CardReadKeyDTO.class).getResultList();
}
@Override
public CardWriteKeyDTO getCardWriteKey() {
// TODO: the decision with "is not null" is dirty. ideally,
// we should know beforehand what kind of card this is and get keys by the name
final String q = "SELECT new de.rwth.idsg.bikeman.psinterface.dto.response." +
"CardWriteKeyDTO(c.name, c.readKey, c.writeKey, c.applicationKey, c.initialApplicationKey) " +
"FROM CardKey c WHERE c.initialApplicationKey is not null";
return em.createQuery(q, CardWriteKeyDTO.class).getSingleResult();
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateAfterBoot(BootNotificationDTO dto) {
// -------------------------------------------------------------------------
// Find the station and update
// -------------------------------------------------------------------------
Station station;
String stationManufacturerId = dto.getStationManufacturerId();
try {
station = findOneByManufacturerId(stationManufacturerId);
station.setFirmwareVersion(dto.getFirmwareVersion());
station.setEndpointAddress(dto.getStationURL());
em.merge(station);
} catch (NoResultException e) {
throw new PsException("Station with manufacturerId '" + stationManufacturerId + "' is not registered", e,
PsErrorCode.NOT_REGISTERED);
}
// -------------------------------------------------------------------------
// Find the slots, and decide whether to Update/Insert/Delete
// -------------------------------------------------------------------------
List<SlotDTO.Boot> stationSlotList = dto.getSlots();
List<String> newList = stationSlotList.parallelStream()
.map(SlotDTO.Boot::getSlotManufacturerId)
.collect(Collectors.toList());
final String q = "SELECT ss.manufacturerId FROM StationSlot ss WHERE ss.station = :station";
List<String> dbList = em.createQuery(q, String.class)
.setParameter("station", station)
.getResultList();
ItemIdComparator<String> idComparator = new ItemIdComparator<>();
idComparator.setDatabaseList(dbList);
idComparator.setNewList(newList);
List<String> updateList = idComparator.getForUpdate();
List<String> insertList = idComparator.getForInsert();
List<String> deleteList = idComparator.getForDelete();
// -------------------------------------------------------------------------
// Update/Insert
// -------------------------------------------------------------------------
final String updateQuery = "UPDATE StationSlot ss " +
"SET ss.isOccupied = :isOccupied, " +
"ss.stationSlotPosition = :slotPosition, " +
"ss.state = de.rwth.idsg.bikeman.domain.OperationState.OPERATIVE, " +
"ss.pedelec = (SELECT p FROM Pedelec p WHERE p.manufacturerId = :pedelecManufacturerId) " +
"WHERE ss.station = :station " +
"AND ss.manufacturerId = :slotManufacturerId";
for (SlotDTO.Boot slot : stationSlotList) {
String slotManufacturerId = slot.getSlotManufacturerId();
String pedelecManufacturerId;
if (Strings.isNullOrEmpty(slot.getPedelecManufacturerId())) {
pedelecManufacturerId = null;
} else {
pedelecManufacturerId = slot.getPedelecManufacturerId();
}
boolean hasPedelec = (pedelecManufacturerId != null);
if (updateList.contains(slotManufacturerId)) {
em.createQuery(updateQuery)
.setParameter("isOccupied", hasPedelec)
.setParameter("slotPosition", slot.getSlotPosition())
.setParameter("pedelecManufacturerId", pedelecManufacturerId)
.setParameter("station", station)
.setParameter("slotManufacturerId", slotManufacturerId)
.executeUpdate();
} else if (insertList.contains(slotManufacturerId)) {
StationSlot newSlot = new StationSlot();
newSlot.setManufacturerId(slotManufacturerId);
newSlot.setStationSlotPosition(slot.getSlotPosition());
newSlot.setStation(station);
newSlot.setIsOccupied(hasPedelec);
newSlot.setState(OperationState.OPERATIVE);
if (hasPedelec) {
try {
Pedelec pedelec = pedelecRepository.findByManufacturerId(pedelecManufacturerId);
newSlot.setPedelec(pedelec);
} catch (DatabaseException e) {
throw new PsException(e.getMessage(), e, PsErrorCode.NOT_REGISTERED);
}
}
em.persist(newSlot);
}
}
// -------------------------------------------------------------------------
// Delete
// -------------------------------------------------------------------------
if (!deleteList.isEmpty()) {
final String deleteQuery = "UPDATE StationSlot ss " +
"SET ss.state = de.rwth.idsg.bikeman.domain.OperationState.DELETED " +
"WHERE ss.manufacturerId IN :slotManufacturerIdList " +
"AND ss.station = :station";
em.createQuery(deleteQuery)
.setParameter("station", station)
.setParameter("slotManufacturerIdList", deleteList)
.executeUpdate();
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateStationStatus(StationStatusDTO dto) {
// -------------------------------------------------------------------------
// Update Station
// -------------------------------------------------------------------------
final String s = "UPDATE Station s SET " +
"s.errorCode = :stationErrorCode, " +
"s.errorInfo = :stationErrorInfo, " +
"s.state = :stationState, " +
"s.updated = :updated " +
"WHERE s.manufacturerId = :stationManufacturerId";
try {
int count = em.createQuery(s)
.setParameter("stationErrorCode", dto.getStationErrorCode())
.setParameter("stationErrorInfo", dto.getStationErrorInfo())
.setParameter("stationState", OperationState.valueOf(dto.getStationState().name()))
.setParameter("updated", new Date(dto.getTimestamp().getMillis()))
.setParameter("stationManufacturerId", dto.getStationManufacturerId())
.executeUpdate();
if (count != 1) {
log.warn("Failed to update status of station with manufacturerId {}", dto.getStationManufacturerId());
}
} catch (Exception e) {
throw new DatabaseException("Failed to update the station status with manufacturerId "
+ dto.getStationManufacturerId(), e);
}
// -------------------------------------------------------------------------
// Update Slots
// -------------------------------------------------------------------------
final String ss = "UPDATE StationSlot ss SET " +
"ss.errorCode = :slotErrorCode, " +
"ss.errorInfo = :slotErrorInfo, " +
"ss.state = :slotState " +
"WHERE ss.manufacturerId = :slotManufacturerId " +
"AND ss.station = (SELECT s FROM Station s WHERE s.manufacturerId = :stationManufacturerId)";
for (SlotDTO.StationStatus slot : dto.getSlots()) {
try {
int count = em.createQuery(ss)
.setParameter("slotErrorCode", slot.getSlotErrorCode())
.setParameter("slotErrorInfo", slot.getSlotErrorInfo())
.setParameter("slotState", OperationState.valueOf(slot.getSlotState().name()))
.setParameter("slotManufacturerId", slot.getSlotManufacturerId())
.setParameter("stationManufacturerId", dto.getStationManufacturerId())
.executeUpdate();
if (count != 1) {
log.warn("Failed to update status of station slot with manufacturerId {}", dto.getStationManufacturerId());
}
} catch (Exception e) {
throw new DatabaseException("Failed to update the slot status with manufacturerId "
+ slot.getSlotManufacturerId(), e);
}
}
}
@Transactional(readOnly = true)
private Station findOneByManufacturerId(String manufacturerId) {
return em.createQuery("SELECT s FROM Station s where s.manufacturerId = :stationManufacturerId", Station.class)
.setParameter("stationManufacturerId", manufacturerId)
.getSingleResult();
}
}