package de.rwth.idsg.bikeman.repository;
import de.rwth.idsg.bikeman.domain.Address;
import de.rwth.idsg.bikeman.domain.Address_;
import de.rwth.idsg.bikeman.domain.OperationState;
import de.rwth.idsg.bikeman.domain.Pedelec;
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.domain.StationSlot_;
import de.rwth.idsg.bikeman.domain.Station_;
import de.rwth.idsg.bikeman.web.rest.dto.modify.CreateEditAddressDTO;
import de.rwth.idsg.bikeman.web.rest.dto.modify.CreateEditStationDTO;
import de.rwth.idsg.bikeman.web.rest.dto.view.ViewErrorDTO;
import de.rwth.idsg.bikeman.web.rest.dto.view.ViewStationDTO;
import de.rwth.idsg.bikeman.web.rest.dto.view.ViewStationSlotDTO;
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.persistence.EntityExistsException;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Path;
import javax.persistence.criteria.Root;
import java.util.List;
/**
* Created by sgokay on 26.05.14.
*/
@Repository
@Slf4j
public class StationRepositoryImpl implements StationRepository {
private enum Operation {CREATE, UPDATE}
@PersistenceContext
private EntityManager em;
@Override
@Transactional(readOnly = true)
public Station findByManufacturerId(String manufacturerId) throws DatabaseException {
final String q = "SELECT s FROM Station s WHERE s.manufacturerId = :manufacturerId";
try {
return em.createQuery(q, Station.class)
.setParameter("manufacturerId", manufacturerId)
.getSingleResult();
} catch (Exception e) {
throw new DatabaseException("Failed to find station with manufacturerId " + manufacturerId, e);
}
}
@Override
@Transactional(readOnly = true)
public List<ViewStationDTO> findAll() throws DatabaseException {
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<ViewStationDTO> criteria = this.getStationQuery(builder, null);
return em.createQuery(criteria).getResultList();
} catch (Exception e) {
throw new DatabaseException("Failed during database operation.", e);
}
}
@Override
@Transactional(readOnly = true)
public ViewStationDTO findOne(long stationId) throws DatabaseException {
CriteriaBuilder builder = em.getCriteriaBuilder();
// get station info
CriteriaQuery<ViewStationDTO> criteria = this.getStationQuery(builder, stationId);
ViewStationDTO stat;
try {
stat = em.createQuery(criteria).getSingleResult();
} catch (Exception e) {
throw new DatabaseException("Failed to find station with stationId " + stationId, e);
}
// get slots for the station
CriteriaQuery<ViewStationSlotDTO> slotCriteria = builder.createQuery(ViewStationSlotDTO.class);
Root<StationSlot> stationSlot = slotCriteria.from(StationSlot.class);
Join<StationSlot, Pedelec> pedelec = stationSlot.join(StationSlot_.pedelec, JoinType.LEFT);
slotCriteria.select(
builder.construct(
ViewStationSlotDTO.class,
stationSlot.get(StationSlot_.stationSlotId),
stationSlot.get(StationSlot_.manufacturerId),
stationSlot.get(StationSlot_.stationSlotPosition),
stationSlot.get(StationSlot_.state),
stationSlot.get(StationSlot_.isOccupied),
pedelec.get(Pedelec_.pedelecId),
pedelec.get(Pedelec_.manufacturerId)
)
).where(builder.equal(stationSlot.get(StationSlot_.station).get(Station_.stationId), stationId));
try {
List<ViewStationSlotDTO> list = em.createQuery(slotCriteria).getResultList();
stat.setSlots(list);
} catch (Exception e) {
throw new DatabaseException("Failed to get slots for the station", e);
}
return stat;
}
@Override
@Transactional(readOnly = true)
public List<ViewErrorDTO> findErrors() throws DatabaseException {
final String q = "SELECT new de.rwth.idsg.bikeman.web.rest.dto.view.ViewErrorDTO(" +
"s.stationId, s.manufacturerId, s.name, s.errorCode, s.errorInfo, s.updated) " +
"FROM Station s where not (s.errorCode = '') and s.errorCode is not null";
try {
return em.createQuery(q, ViewErrorDTO.class)
.getResultList();
} catch (Exception e) {
throw new DatabaseException("Failed to find stations with error messages");
}
}
@Override
@Transactional(readOnly = true)
public String getEndpointAddress(long stationId) throws DatabaseException {
final String q = "SELECT s.endpointAddress FROM Station s WHERE s.stationId = :stationId";
try {
return em.createQuery(q, String.class)
.setParameter("stationId", stationId)
.getSingleResult();
} catch (Exception e) {
throw new DatabaseException("Failed to find station with stationId " + stationId, e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void create(CreateEditStationDTO dto) throws DatabaseException {
Station station = new Station();
setFields(station, dto, Operation.CREATE);
try {
em.persist(station);
log.debug("Created new manager {}", station);
} catch (EntityExistsException e) {
throw new DatabaseException("This station exists already.", e);
} catch (Exception e) {
throw new DatabaseException("Failed to create a new station.", e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void update(CreateEditStationDTO dto) throws DatabaseException {
final Long stationId = dto.getStationId();
if (stationId == null) {
return;
}
Station station = getStationEntity(stationId);
setFields(station, dto, Operation.UPDATE);
try {
em.merge(station);
log.debug("Updated station {}", station);
} catch (Exception e) {
throw new DatabaseException("Failed to update station with stationId " + stationId, e);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public void changeSlotState(long stationId, int slotPosition, OperationState state) {
final String query = "UPDATE StationSlot ss " +
"SET ss.state = :state " +
"WHERE ss.stationSlotPosition = :slotPosition " +
"AND ss.station = (SELECT s FROM Station s WHERE s.stationId = :stationId)";
int count = em.createQuery(query)
.setParameter("state", state)
.setParameter("slotPosition", slotPosition)
.setParameter("stationId", stationId)
.executeUpdate();
if (count == 1) {
log.debug("[StationId: {}] Slot with position {} is updated", stationId, slotPosition);
} else {
log.error("[StationId: {}] Slot with position {} update FAILED", stationId, slotPosition);
}
}
/**
* Returns a station, or throws exception when no station exists.
*/
@Transactional(readOnly = true)
private Station getStationEntity(long stationId) throws DatabaseException {
Station station = em.find(Station.class, stationId);
if (station == null) {
throw new DatabaseException("No station with stationId " + stationId);
} else {
return station;
}
}
/**
* This method sets the fields of the station to the values in DTO.
* <p/>
* Important: The ID is not set!
*/
private void setFields(Station station, CreateEditStationDTO dto, Operation operation) {
station.setManufacturerId(dto.getManufacturerId());
station.setName(dto.getName());
station.setLocationLatitude(dto.getLocationLatitude());
station.setLocationLongitude(dto.getLocationLongitude());
station.setNote(dto.getNote());
station.setState(dto.getState());
switch (operation) {
case CREATE:
// for create (brand new address entity)
Address newAdd = new Address();
CreateEditAddressDTO newDtoAdd = dto.getAddress();
newAdd.setStreetAndHousenumber(newDtoAdd.getStreetAndHousenumber());
newAdd.setZip(newDtoAdd.getZip());
newAdd.setCity(newDtoAdd.getCity());
newAdd.setCountry(newDtoAdd.getCountry());
station.setAddress(newAdd);
break;
case UPDATE:
// for edit (keep the address ID)
Address add = station.getAddress();
CreateEditAddressDTO dtoAdd = dto.getAddress();
add.setStreetAndHousenumber(dtoAdd.getStreetAndHousenumber());
add.setZip(dtoAdd.getZip());
add.setCity(dtoAdd.getCity());
add.setCountry(dtoAdd.getCountry());
break;
}
}
/**
* This method returns the query to get information of all the stations or only the specified station (by stationId)
*/
private CriteriaQuery<ViewStationDTO> getStationQuery(CriteriaBuilder builder, Long stationId) {
CriteriaQuery<ViewStationDTO> criteria = builder.createQuery(ViewStationDTO.class);
Root<Station> station = criteria.from(Station.class);
Join<Station, StationSlot> stationSlot = station.join(Station_.stationSlots, JoinType.LEFT);
Join<Station, Address> address = station.join(Station_.address, JoinType.LEFT);
Path<Boolean> occ = stationSlot.get(StationSlot_.isOccupied);
criteria.select(
builder.construct(
ViewStationDTO.class,
station.get(Station_.stationId),
station.get(Station_.manufacturerId),
station.get(Station_.name),
address.get(Address_.streetAndHousenumber),
address.get(Address_.zip),
address.get(Address_.city),
address.get(Address_.country),
station.get(Station_.locationLatitude),
station.get(Station_.locationLongitude),
station.get(Station_.note),
station.get(Station_.state),
builder.sum(builder.<Integer>selectCase()
.when(builder.isFalse(occ), 1)
.otherwise(0)
),
builder.count(occ)
)
);
if (stationId != null) {
criteria.where(builder.equal(station.get(Station_.stationId), stationId));
}
criteria.groupBy(station.get(Station_.stationId), address.get(Address_.addressId));
return criteria;
}
}