package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service; import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidOperationException; import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao.OperationDAO; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao.VehicleDAO; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation.Severity; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation.Status; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.VehicleType; import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @Service public class OperationServiceImpl implements OperationService { private static final Logger LOG = LoggerFactory.getLogger(OperationServiceImpl.class); private final OperationDAO operationDAO; private final VehicleDAO vehicleDAO; private final VehicleService vehicleService; public OperationServiceImpl( OperationDAO operationDAO, VehicleDAO vehicleDAO, VehicleService vehicleService) { this.operationDAO = operationDAO; this.vehicleDAO = vehicleDAO; this.vehicleService = vehicleService; } @Override public long add(Operation o) throws InvalidOperationException, ServiceException { if (o.created() != null) { throw new InvalidOperationException("Erstellungszeitpunkt darf nicht gesetzt sein"); } if (o.severity() != null) { throw new InvalidOperationException("Der Schweregrad darf nicht gesetzt sein"); } if (o.id() != 0) { throw new InvalidOperationException("Einsatz-ID muss 0 sein"); } if (o.status() != Status.ACTIVE) LOG.info("Status was set but will be overridden"); // TODO: nullable instead?? try { for (long id : (Iterable) o.vehicles().stream().map(Vehicle::id)::iterator) { Vehicle v = vehicleDAO.get(id); VehicleServiceImpl.validateVehicle(v); if (v.status() != Vehicle.Status.FREI_FUNK && v.status() != Vehicle.Status.FREI_WACHE) throw new InvalidOperationException("Fahrzeug nicht verfügbar: " + v.status()); } validateOperation(o); return operationDAO.add( o.toBuilder() .created(Instant.now()) .severity(extractSeverityFromOpCode(o.opCode())) .status(Status.ACTIVE) .build()); } catch (PersistenceException e) { throw new ServiceException(e); } catch (InvalidVehicleException e) { // already logged as invalid vehicle throw new InvalidOperationException("Enthaltenes Fahrzeug ist ungültig", e); } catch (ElementNotFoundException e) { throw new InvalidOperationException("Enthaltenes Fahrzeug existiert nicht", e); } } @Override public void requestVehicles(long operationId, Set vehicleIds) throws InvalidOperationException, InvalidVehicleException, ServiceException { Set vs = new HashSet<>(); try { if (operationId <= 0) { throw new InvalidOperationException("Einsatz-ID ist ungültig"); } Operation o = operationDAO.get(operationId); validateOperation(o); if (o.opCode().trim().isEmpty() || extractSeverityFromOpCode(o.opCode()) != o.severity()) { throw new InvalidOperationException("Einsatzcode ist ungültig"); } if (o.status() != Status.ACTIVE) { throw new InvalidOperationException("Einsatz ist ungültig"); } if (o.created() == null) { throw new InvalidOperationException("Erstellungszeitpunkt darf nicht leer sein"); } for (Long id : vehicleIds) { if (id <= 0) { throw new InvalidVehicleException("Fahrzeug-ID ist ungültig"); } try { Vehicle v = vehicleDAO.get(id); VehicleServiceImpl.validateVehicle(v); if (v.status() != Vehicle.Status.FREI_FUNK && v.status() != Vehicle.Status.FREI_WACHE) throw new InvalidOperationException( "Fahrzeug nicht verfügbar: " + v.status()); vs.add(v); } catch (ElementNotFoundException e) { throw new InvalidVehicleException("VehicleId ist invalid"); } } vs.addAll(o.vehicles()); if (vs.equals(o.vehicles())) return; operationDAO.update(o.toBuilder().vehicles(vs).build()); } catch (ElementNotFoundException e) { throw new InvalidOperationException("Kein Einsatz mit dieser ID existiert"); } catch (PersistenceException e) { throw new ServiceException(e); } } @Override public void complete(long operationId, Status status) throws InvalidOperationException, ServiceException { try { Operation o = operationDAO.get(operationId); operationDAO.update(o.toBuilder().status(status).build()); } catch (ElementNotFoundException e) { throw new InvalidOperationException(e); } catch (PersistenceException e) { throw new ServiceException(e); } } @Override public SortedSet rankVehicles(String opCode) throws InvalidOperationException, ServiceException { Set vehicles = vehicleService.list(EnumSet.complementOf(EnumSet.of(Vehicle.Status.ABGEMELDET))); List> priorities = new ArrayList<>(); Predicate ktw = v -> v.type() == VehicleType.KTW; Predicate rtwNoNEF = v -> v.type() == VehicleType.RTW && !v.hasNef(); Predicate rtwNEF = v -> v.type() == VehicleType.RTW && v.hasNef(); Predicate nef = v -> v.type() == VehicleType.NEF; Predicate nah = v -> v.type() == VehicleType.NAH; switch (extractSeverityFromOpCode(opCode)) { case A: // fallthrough case B: // fallthrough case O: priorities.add(ktw); priorities.add(rtwNoNEF); priorities.add(rtwNEF); break; case C: priorities.add(rtwNEF); priorities.add(rtwNoNEF); priorities.add(ktw); break; case D: priorities.add(rtwNEF); priorities.add(nef); priorities.add(nah); priorities.add(rtwNoNEF); priorities.add(ktw); break; case E: priorities.add(nah); priorities.add(nef); priorities.add(rtwNEF); priorities.add(rtwNoNEF); priorities.add(ktw); break; } Comparator vehicleComparator = (v1, v2) -> { for (Predicate priority : priorities) { if (priority.test(v1)) { return -1; } if (priority.test(v2)) { return +1; } } return 0; }; Supplier> supplier = () -> new TreeSet<>(vehicleComparator); return vehicles.stream().collect(Collectors.toCollection(supplier)); } @Override public Set list(EnumSet statuses) throws ServiceException { try { Set operations = operationDAO.list(statuses); for (Operation o : operations) validateOperation(o); return operations; } catch (PersistenceException e) { throw new ServiceException(e); } catch (InvalidOperationException e) { // database returned invalid values throw new ServiceException("DB returned invalid operation", e); } } private static void validateOperation(Operation o) throws InvalidOperationException { if (o.vehicles().isEmpty()) { throw new InvalidOperationException( "Es muss mindestens ein Fahrzeug ausgewählt werden!"); } for (Vehicle v : o.vehicles()) { try { VehicleServiceImpl.validateVehicle(v); } catch (InvalidVehicleException e) { throw new InvalidOperationException("Fahrzeug " + v.name() + " ist ungültig", e); } // TODO: validate if NEF/RTW/NAH conditions? } Instant created = o.created(); if (created != null && created.isAfter(Instant.now())) { throw new InvalidOperationException("Einsatz wurde in der Zukunft erstellt"); } if (o.destination() == null || o.destination().trim().isEmpty()) { throw new InvalidOperationException("Adresse darf nicht leer sein"); } if (o.destination().length() > 100) { throw new InvalidOperationException("Adresse darf 100 Zeichen nicht überschreiten"); } if (o.additionalInfo() != null && o.additionalInfo().length() > 100) { throw new InvalidOperationException("Anmerkung darf 100 Zeichen nicht überschreiten"); } } private static final Pattern opCodePattern = Pattern.compile("(?:\\w{1,3}-\\d{0,2})([ABCDEO])(?:.*)"); private static Severity extractSeverityFromOpCode(String opCode) throws InvalidOperationException { Matcher m = opCodePattern.matcher(opCode); if (!m.matches()) { throw new InvalidOperationException("Einsatzcode ist ungültig"); } return Severity.valueOf(m.group(1)); } }