diff options
Diffstat (limited to 'src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol')
37 files changed, 4388 insertions, 0 deletions
diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ArchiveOperationController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ArchiveOperationController.java new file mode 100644 index 0000000..212dba6 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ArchiveOperationController.java @@ -0,0 +1,281 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +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.service.OperationService; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import java.io.IOException; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Arrays; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import javafx.fxml.FXML; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class ArchiveOperationController { + +    private static final Logger LOG = LoggerFactory.getLogger(ArchiveOperationController.class); +    @FXML private TextField txtSearch; + +    @FXML private ImageView imvVehicleDetail; +    @FXML private Label lblStatus; +    @FXML private AnchorPane apMainDetails; +    @FXML private Label lblOperations; +    @FXML private Label lblCompleted; +    @FXML private Label lblCancelled; +    @FXML private AnchorPane backApMain; +    @FXML private AnchorPane backApDetails; +    @FXML private AnchorPane archiveOperationAP; +    @FXML private AnchorPane apDetails; +    @FXML private Label lblCodeHeader; +    @FXML private Label lblOpCode; +    @FXML private Label lblVehicles; +    @FXML private Label lblDate; +    @FXML private Label lblAddress; +    @FXML private FlowPane fpVehicles; +    private final OperationService operationService; +    @FXML private FlowPane archiveOperationFlowPane; +    private final CreateOperationController createOperationController; +    private Set<Operation> list = new HashSet<>(); +    private final SpringFXMLLoader fxmlLoader; + +    public ArchiveOperationController( +            OperationService operationService, +            CreateOperationController createOperationController, +            SpringFXMLLoader fxmlLoader) { +        this.operationService = operationService; +        this.createOperationController = createOperationController; +        this.fxmlLoader = fxmlLoader; +    } + +    @FXML +    private void initialize() { +        update(); +    } + +    public void update() { +        archiveOperationFlowPane.getChildren().clear(); +        list.clear(); +        try { +            list.addAll(operationService.list(EnumSet.of(Status.CANCELLED, Status.COMPLETED))); +            long cancelledAmount = 0; +            long completedAmount = 0; +            for (Operation operation : list) { +                if (operation.status() == Status.CANCELLED) cancelledAmount++; +                else completedAmount++; +            } +            lblCancelled.setText("storniert: " + cancelledAmount); +            lblCompleted.setText("abgeschlossen: " + completedAmount); +            lblOperations.setText("Einsätze: " + list.size()); +        } catch (ServiceException e) { +            LOG.error("ServiceException in update().", e); +            showServiceExceptionAlertAndWait("Die Einsätze konnten nicht geladen werden!"); +            ; +        } +        setFlowPane(list); +    } + +    public void update(Set<Operation> operations) { +        long cancelledAmount = 0; +        long completedAmount = 0; +        for (Operation operation : operations) { +            if (operation.status() == Status.CANCELLED) cancelledAmount++; +            else completedAmount++; +        } +        lblCancelled.setText("storniert: " + cancelledAmount); +        lblCompleted.setText("abgeschlossen: " + completedAmount); +        lblOperations.setText("Einsätze: " + operations.size()); +        setFlowPane(operations); +    } + +    private void setFlowPane(Set<Operation> operations) { +        try { +            archiveOperationFlowPane.getChildren().clear(); +            for (Operation operation : sortSet(operations)) { +                OperationInArchiveController opInAController = +                        OperationInArchiveController.create(); +                opInAController.set(operation); +                opInAController +                        .getRoot() +                        .setOnMouseClicked( +                                event -> { +                                    detailOperation = operation; +                                    backApMain.setVisible(false); +                                    apMainDetails.setVisible(false); +                                    backApDetails.setVisible(true); +                                    setOperation(); +                                    setDetailsVisible(true); +                                    imvVehicleDetail.setImage(new Image("/images/Vehicle.png")); +                                }); +                archiveOperationFlowPane.getChildren().add(opInAController.getRoot()); +            } +        } catch (IOException e) { +            LOG.error("IOException in setFlowPane(). ", e); +            showServiceExceptionAlertAndWait("Die Elemente konnten nicht geladen werden!"); +        } +    } + +    private Operation detailOperation; + +    private List<Operation> sortSet(Set<Operation> operationsSet) { +        Operation[] array = operationsSet.toArray(new Operation[operationsSet.size()]); +        for (int i = array.length - 1; i > 0; i--) { +            for (int j = 0; j < i; j++) { +                LocalDateTime first = +                        LocalDateTime.ofInstant( +                                Objects.requireNonNull(array[j].created()), ZoneOffset.UTC); +                LocalDateTime second = +                        LocalDateTime.ofInstant( +                                Objects.requireNonNull(array[j + 1].created()), ZoneOffset.UTC); +                if (first.isBefore(second)) { +                    Operation help = array[j]; +                    array[j] = array[j + 1]; +                    array[j + 1] = help; +                } +            } +        } +        return Arrays.asList(array); +    } + +    private void setOperation() { +        lblCodeHeader.setText(detailOperation.opCode()); +        if (detailOperation.created() != null) { +            LocalDateTime dateTime = +                    LocalDateTime.ofInstant( +                            Objects.requireNonNull(detailOperation.created()), ZoneOffset.UTC); +            lblDate.setText( +                    "am " +                            + dateTime.getDayOfMonth() +                            + "." +                            + dateTime.getMonth().getValue() +                            + "." +                            + dateTime.getYear()); +        } else { +            lblDate.setText("---"); +        } +        lblStatus.setText( +                "Status: " +                        + (detailOperation.status() == Status.CANCELLED +                                ? "storniert" +                                : "abgeschlossen")); +        lblOpCode.setText(detailOperation.opCode()); +        Collection<String> elements = +                detailOperation.vehicles().stream().map(Vehicle::name).collect(Collectors.toList()); +        String result = String.join(", ", elements); + +        lblVehicles.setText(result); +        lblAddress.setText(detailOperation.destination()); + +        fpVehicles.getChildren().clear(); +        try { +            for (Vehicle vehicle : detailOperation.vehicles()) { +                DetailArchiveOperationController controller = null; + +                controller = DetailArchiveOperationController.create(fxmlLoader); + +                controller.set(vehicle, detailOperation); +                fpVehicles.getChildren().add(controller.getRoot()); +            } +        } catch (IOException e) { +            LOG.error("IOException in setOperation(). ", e); +            showServiceExceptionAlertAndWait("Die Element konnte nicht geladen werden!"); +        } +    } + +    private void setDetailsVisible(boolean b) { +        apDetails.setVisible(b); +    } + +    public void backClicked() { +        LOG.debug("Hyperlink \"Zurück\" in archive detail view clicked."); +        fpVehicles.getChildren().clear(); +        setDetailsVisible(false); +        backApDetails.setVisible(false); +        apMainDetails.setVisible(true); +        backApMain.setVisible(true); +    } + +    public void backToMain() { +        LOG.debug("Hyperlink \"Zurück\" in archive main view clicked."); +        this.setVisible(false); +        createOperationController.setVisible(true); +    } + +    void setVisible(boolean b) { +        archiveOperationAP.setVisible(b); +        backApMain.setVisible(b); +        apMainDetails.setVisible(b); +    } + +    @FXML +    public void searchInput() { +        LOG.debug("Search for operations in archive detail view started."); +        String text = txtSearch.getText(); +        Set<Operation> chosenOperations = new HashSet<>(); +        if (emptyText(text)) update(); +        else { +            for (Operation operation : list) { +                if (checkEquality(operation, text)) chosenOperations.add(operation); +            } +            update(chosenOperations); +        } +    } + +    private boolean emptyText(String text) { +        if (text == null) return true; +        text = text.replaceAll("\\s+", ""); +        return text.isEmpty(); +    } + +    private boolean checkEquality(Operation operation, String text) { +        if (isEqual(text, operation.opCode()) +                || isEqual(text, operation.destination()) +                || isEqual(text, reformDateToString(operation.created()))) return true; +        for (Vehicle vehicle : operation.vehicles()) { +            if (isEqual(text, vehicle.name())) return true; +        } +        return false; +    } + +    private String reformDateToString(Instant time) { +        LocalDateTime dateTime = +                LocalDateTime.ofInstant(Objects.requireNonNull(time), ZoneOffset.UTC); +        return "am " +                + dateTime.getDayOfMonth() +                + "." +                + dateTime.getMonth().getValue() +                + "." +                + dateTime.getYear(); +    } + +    private boolean isEqual(String text, String realText) { +        for (int i = 0; (i + text.length()) < realText.length(); i++) { +            StringBuilder result = new StringBuilder(); +            for (int j = i; j < i + text.length(); j++) { +                result.append(realText.charAt(j)); +            } +            if ((text.toLowerCase()).equals(result.toString().toLowerCase())) return true; +        } +        return false; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateCarController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateCarController.java new file mode 100644 index 0000000..4da46a2 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateCarController.java @@ -0,0 +1,231 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showSuccessAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showValidationErrorAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.ConstructionType; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.VehicleType; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.VehicleService; +import java.io.IOException; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javafx.collections.FXCollections; +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class CreateCarController { + +    @FXML private AnchorPane createCarAP; +    @FXML private ChoiceBox<String> cmbCtype; +    @FXML private ChoiceBox<String> cmbTyp; + +    @FXML private Button btnCreate; +    @FXML private CheckBox cbxNEF; +    @FXML private FlowPane fpVehicleList; +    private final CreateOperationController createOperationController; + +    private static final Logger LOG = LoggerFactory.getLogger(CreateCarController.class); +    private final VehicleService vehicleService; +    private boolean update = false; +    private long vid = -1; + +    private Vehicle chooseVehicle; + +    public CreateCarController( +            CreateOperationController createOperationController, VehicleService vehicleService) { +        this.createOperationController = createOperationController; +        this.vehicleService = vehicleService; +    } + +    @FXML +    private void initialize() { +        fpVehicleList.setHgap(5); +        fpVehicleList.setVgap(5); + +        cmbCtype.setItems( +                FXCollections.observableArrayList( +                        Stream.of( +                                        ConstructionType.NORMAL, +                                        ConstructionType.MITTELHOCHDACH, +                                        ConstructionType.HOCHDACH) +                                .map(Enum::toString) +                                .collect(Collectors.toList()))); +        cmbCtype.setValue(ConstructionType.NORMAL.toString()); +        cmbTyp.setItems( +                FXCollections.observableArrayList( +                        Stream.of( +                                        VehicleType.BKTW, +                                        VehicleType.KTW_B, +                                        VehicleType.KTW, +                                        VehicleType.RTW, +                                        VehicleType.NEF, +                                        VehicleType.NAH) +                                .map(Enum::toString) +                                .collect(Collectors.toList()))); +        cmbTyp.setValue(VehicleType.BKTW.toString()); + +        updateVehiclePane(); +    } + +    @FXML +    private void createCar(ActionEvent actionEvent) { + +        if (!update) { +            LOG.debug("Button \"Erstellen\" clicked."); +            Vehicle vehicle = +                    Vehicle.builder() +                            .constructionType(parseConstructionType()) +                            .type(parseType()) +                            .name("") +                            .status(Status.ABGEMELDET) +                            .hasNef(cbxNEF.isSelected()) +                            .build(); +            try { +                vehicleService.add(vehicle); +                setToStart(); +            } catch (InvalidVehicleException e) { +                LOG.debug("Validation of Vehicle failed"); +                showValidationErrorAlertAndWait(e.getMessage()); +                setToStart(); +                return; +            } catch (ServiceException e) { +                LOG.error("ServiceException in createCar(). ", e); +                showServiceExceptionAlertAndWait( +                        "Ein Fehler beim Erstellen des Fahrzeuges ist aufgetreten."); +                setToStart(); +                return; +            } +            showSuccessAlertAndWait("Auto wurde erfolgreich angelegt."); +        } else { +            LOG.debug("Button \"Speichern\" clicked."); +            try { +                Vehicle vehicle = +                        Vehicle.builder() +                                .id(vid) +                                .constructionType(parseConstructionType()) +                                .type(parseType()) +                                .name("") +                                .status(Status.ABGEMELDET) +                                .hasNef(cbxNEF.isSelected()) +                                .build(); +                vehicleService.update(vehicle); +                setToStart(); +                chooseVehicle = null; +            } catch (InvalidVehicleException e) { +                LOG.debug("Validation of Vehicle failed"); +                showValidationErrorAlertAndWait(e.getMessage()); +                setToStart(); +                return; +            } catch (ServiceException e) { +                LOG.error("ServiceException in createCar(). ", e); +                showServiceExceptionAlertAndWait(e.getMessage()); +                setToStart(); +                return; +            } +            showSuccessAlertAndWait("Auto wurde erfolgreich bearbeitet."); +        } + +        updateVehiclePane(); +    } + +    private ConstructionType parseConstructionType() { +        if (cmbCtype.getSelectionModel().getSelectedItem() == null) { +            return ConstructionType.NORMAL; +        } +        return ConstructionType.valueOf(cmbCtype.getSelectionModel().getSelectedItem()); +    } + +    private VehicleType parseType() { +        if (cmbTyp.getSelectionModel().getSelectedItem() == null) { +            return VehicleType.BKTW; +        } +        return VehicleType.valueOf(cmbTyp.getSelectionModel().getSelectedItem()); +    } + +    private void setToStart() { +        btnCreate.setText("Erstellen"); +        cbxNEF.setSelected(false); +        cmbTyp.setValue(VehicleType.BKTW.name()); +        cmbCtype.setValue(ConstructionType.NORMAL.name()); +        update = false; +    } + +    private void updateVehicle(Vehicle vehicle) { + +        cmbCtype.setValue(vehicle.constructionType().name()); +        cmbTyp.setValue(vehicle.type().name()); +        cbxNEF.setSelected(vehicle.hasNef()); +        btnCreate.setText("Speichern"); +        vid = vehicle.id(); +        update = true; +        chooseVehicle = vehicle; +    } + +    public void setVisible(boolean b) { +        createCarAP.setVisible(b); +    } + +    @FXML +    private void backToMain() { +        LOG.debug("Hyperlink \"zurück\" clicked."); +        this.setVisible(false); +        createOperationController.setVisible(true); +    } + +    void updateVehiclePane() { +        try { +            fpVehicleList.getChildren().clear(); + +            Set<Vehicle> vehicles; + +            vehicles = vehicleService.list(EnumSet.of(Status.ABGEMELDET)); + +            for (Vehicle vehicle : vehicles) { +                VehiclePaneController controller = VehiclePaneController.createVehiclePane(); + +                controller.setData(vehicle, false, false); +                controller +                        .getRootElement() +                        .setOnMouseClicked( +                                event -> { +                                    if (event.getButton().equals(MouseButton.PRIMARY)) { +                                        if (chooseVehicle == null || vehicle == chooseVehicle) { +                                            if (update == false) { +                                                chooseVehicle = vehicle; +                                                updateVehicle(vehicle); +                                                controller.setSelected(true); +                                            } else { +                                                setToStart(); +                                                controller.setSelected(false); + +                                                chooseVehicle = null; +                                            } +                                        } +                                    } +                                }); + +                fpVehicleList.getChildren().add(controller.getRootElement()); +            } +        } catch (ServiceException | IOException e) { +            LOG.error("Exception in updateVehiclePane(). ", e); +            showServiceExceptionAlertAndWait(e.getMessage()); +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateNewEmployeeController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateNewEmployeeController.java new file mode 100644 index 0000000..3e0240c --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateNewEmployeeController.java @@ -0,0 +1,174 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showSuccessAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showValidationErrorAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidEmployeeException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee.EducationLevel; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.EmployeeService; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader.FXMLWrapper; +import java.io.IOException; +import java.time.LocalDate; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.CheckBox; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; + +@Controller +@Scope("prototype") +public class CreateNewEmployeeController { + +    private static final Logger LOG = LoggerFactory.getLogger(CreateNewEmployeeController.class); +    private final EmployeeService employeeService; + +    @FXML private Label lblHeader; +    @FXML private CheckBox inputIsDriver; +    @FXML private CheckBox inputIsPilot; +    @FXML private TextField inputName; +    @FXML private ChoiceBox<String> inputQualification; +    @FXML private Button btnCreate; + +    private Node rootElement; +    private Employee employee; +    private boolean isEdit; + +    private Runnable consumerCancelClicked; +    private Runnable consumerCreateClicked; + +    public CreateNewEmployeeController(EmployeeService employeeService) { +        this.employeeService = employeeService; +    } + +    @FXML +    private void initialize() { +        inputQualification.setItems( +                FXCollections.observableArrayList( +                        Stream.of( +                                        EducationLevel.RS, +                                        EducationLevel.NFS, +                                        EducationLevel.NKV, +                                        EducationLevel.NKA, +                                        EducationLevel.NKI, +                                        EducationLevel.NA) +                                .map(Enum::toString) +                                .collect(Collectors.toList()))); + +        inputQualification.setValue(EducationLevel.RS.toString()); +        employee = +                Employee.builder() +                        .name("") +                        .educationLevel(EducationLevel.RS) +                        .isDriver(false) +                        .isPilot(false) +                        .birthday(LocalDate.MIN) +                        .build(); +    } + +    @FXML +    private void onCancelClicked() { +        LOG.debug("Hyperlink \"abbrechen\" clicked."); +        if (consumerCancelClicked != null) { +            consumerCancelClicked.run(); +        } +    } + +    @FXML +    private void onCreateClicked() { +        LOG.debug("Button {} clicked.", btnCreate.getText()); + +        employee = +                employee.toBuilder() +                        .name(inputName.getText()) +                        .educationLevel(parseEducationLevel()) +                        .birthday(LocalDate.MIN) // TODO: change UI to include birthday field +                        .isDriver(inputIsDriver.isSelected()) +                        .isPilot(inputIsPilot.isSelected()) +                        .build(); + +        try { +            if (isEdit) { +                employeeService.update(employee); +            } else { +                employeeService.add(employee); +            } +        } catch (InvalidEmployeeException e) { +            LOG.debug("Validation for Employee failed"); +            showValidationErrorAlertAndWait(e.getMessage()); +            return; +        } catch (ServiceException e) { +            LOG.error("ServiceException in onCreateClicked(). ", e); +            showServiceExceptionAlertAndWait( +                    "Der Eintrag konnte nicht gespeichert werden. Bitte versuchen Sie es erneut."); +            return; +        } + +        showSuccessAlertAndWait( +                "Der/die MitarbeiterIn wurde erfolgreich angelegt und gespeichert!"); + +        if (consumerCreateClicked != null) { +            consumerCreateClicked.run(); +        } +    } + +    private EducationLevel parseEducationLevel() { +        if (inputQualification.getSelectionModel().getSelectedItem() == null) { +            return EducationLevel.RS; +        } +        return EducationLevel.valueOf(inputQualification.getSelectionModel().getSelectedItem()); +    } + +    private void setData(Employee employee) { +        isEdit = true; +        this.employee = employee; +        inputName.setText(employee.name()); +        inputQualification.setValue(employee.educationLevel().name()); +        inputIsDriver.setSelected(employee.isDriver()); +        inputIsPilot.setSelected(employee.isPilot()); +        lblHeader.setText("Person bearbeiten"); +        btnCreate.setText("Speichern"); +    } + +    public static CreateNewEmployeeController createCreateNewEmployeeController( +            SpringFXMLLoader fxmlLoader, Employee employee) throws IOException { +        CreateNewEmployeeController controller = createCreateNewEmployeeController(fxmlLoader); +        controller.setData(employee); +        return controller; +    } + +    public static CreateNewEmployeeController createCreateNewEmployeeController( +            SpringFXMLLoader fxmlLoader) throws IOException { +        FXMLWrapper<Object, CreateNewEmployeeController> wrapper = +                fxmlLoader.loadAndWrap( +                        "/fxml/createNewEmployee.fxml", CreateNewEmployeeController.class); +        Node root = (Node) wrapper.getLoadedObject(); +        CreateNewEmployeeController controller = wrapper.getController(); +        controller.rootElement = root; +        return controller; +    } + +    public Node getRootElement() { +        return rootElement; +    } + +    public void setConsumerCancelClicked(Runnable consumerCancelClicked) { +        this.consumerCancelClicked = consumerCancelClicked; +    } + +    public void setConsumerCreateClicked(Runnable consumerCreateClicked) { +        this.consumerCreateClicked = consumerCreateClicked; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateOperationController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateOperationController.java new file mode 100644 index 0000000..f06b43f --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CreateOperationController.java @@ -0,0 +1,361 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showSuccessAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showValidationErrorAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidOperationException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidRegistrationException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation.Status; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.OperationService; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.RegistrationService; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.VehicleService; +import java.io.IOException; +import java.util.EnumSet; +import java.util.LinkedList; +import java.util.Set; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.control.ListCell; +import javafx.scene.control.ListView; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import javafx.scene.layout.GridPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class CreateOperationController { + +    private static final Logger LOG = LoggerFactory.getLogger(CreateOperationController.class); + +    public AnchorPane apCreateOperation; +    @FXML private GridPane grdWindowContainer; +    @FXML private TextField txtCode; +    @FXML private TextField txtAddress; +    @FXML private TextField txtNote; +    @FXML private Button btnCreateOperation; +    @FXML private ListView<Vehicle> lvVehicles; +    @FXML private ListView<Operation> lvActiveOperations; +    @FXML private Label lblChosenVehicles; +    @FXML private AnchorPane apInvisible; +    @FXML private OperationDetailsController operationDetailsController; +    @FXML private ManageEmployeesController manageEmployeesController; +    @FXML private CreateCarController createCarController; +    @FXML private RegistrationWindowController registrationWindowController; +    @FXML private ArchiveOperationController archiveOperationController; +    @FXML private FlowPane fpVehicles; + +    private LinkedList<Vehicle> chosenVehicles = new LinkedList<>(); + +    private final OperationService operationService; +    private final VehicleService vehicleService; +    private final RegistrationService registrationService; + +    public CreateOperationController( +            OperationService operationService, +            VehicleService vehicleService, +            RegistrationService registrationService) { +        this.operationService = operationService; +        this.vehicleService = vehicleService; +        this.registrationService = registrationService; +    } + +    @FXML +    private void initialize() { + +        lblChosenVehicles.setText("keine ausgewählt"); +        lvActiveOperations.setCellFactory(param -> generateOpCodeListItem()); + +        lvActiveOperations.setOnMouseClicked( +                event -> { +                    if (event.getClickCount() == 2) { +                        if (lvActiveOperations.getSelectionModel().getSelectedItem() == null) { +                            return; +                        } +                        openDetailsWindow(lvActiveOperations.getSelectionModel().getSelectedItem()); +                    } +                }); + +        setVisible(true); +        createCarController.setVisible(false); +        registrationWindowController.setVisible(false); + +        updateList(); +    } + +    public void updateList() { +        try { +            fpVehicles.getChildren().clear(); + +            // TODO: this should probably be handled differently +            Set<Vehicle> vehicles; +            if (txtCode.getText().isEmpty()) { +                vehicles = +                        vehicleService.list( +                                EnumSet.complementOf(EnumSet.of(Vehicle.Status.ABGEMELDET))); +            } else { +                vehicles = operationService.rankVehicles(txtCode.getText()); +            } + +            for (Vehicle vehicle : vehicles) { +                VehiclePaneController controller = VehiclePaneController.createVehiclePane(); + +                controller.setData(vehicle, true, false); +                controller +                        .getRootElement() +                        .setOnMouseClicked( +                                event -> { +                                    if (event.getButton().equals(MouseButton.SECONDARY)) { +                                        createContextMenu( +                                                        vehicle, +                                                        vehicleService, +                                                        registrationService) +                                                .show( +                                                        controller.getRootElement(), +                                                        event.getScreenX(), +                                                        event.getScreenY()); +                                    } else { +                                        if (chosenVehicles.contains(vehicle)) { +                                            chosenVehicles.remove(vehicle); +                                            controller.setSelected(false); +                                        } else { +                                            chosenVehicles.add(vehicle); +                                            controller.setSelected(true); +                                        } + +                                        StringBuilder result = new StringBuilder(); +                                        for (int i = 0; i < chosenVehicles.size(); i++) { +                                            if (i == chosenVehicles.size() - 1) { +                                                result.append(chosenVehicles.get(i).name()); +                                            } else { +                                                result.append(chosenVehicles.get(i).name()) +                                                        .append(", "); +                                            } +                                        } +                                        if (result.toString().equals("")) { +                                            lblChosenVehicles.setText("keine ausgewählt"); +                                        } else { +                                            lblChosenVehicles.setText(result.toString()); +                                        } +                                    } +                                }); + +                if (chosenVehicles.stream().anyMatch(v -> v.id() == vehicle.id())) +                    controller.setSelected(true); + +                fpVehicles.getChildren().add(controller.getRootElement()); +            } +        } catch (ServiceException | IOException e) { +            LOG.error("Exception in updateList(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Erstellen des Ranking ist ein Fehler aufgetreten."); +        } catch (InvalidOperationException e) { +            LOG.debug("Validation error in updateList(). ", e); +            showValidationErrorAlertAndWait(e.getMessage()); +        } +        try { +            lvActiveOperations.setItems( +                    FXCollections.observableArrayList( +                            operationService.list(EnumSet.of(Status.ACTIVE)))); +        } catch (ServiceException e) { +            LOG.error("ServiceException in updateList(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Holen der aktiven Einsätze ist ein Fehler aufgetreten"); +        } +    } + +    private ContextMenu createContextMenu( +            Vehicle data, VehicleService vehicleService, RegistrationService registrationService) { +        ContextMenu menu = new ContextMenu(); + +        for (Vehicle.Status status : Vehicle.Status.values()) { +            if (status == Vehicle.Status.ABGEMELDET) continue; + +            MenuItem mi = new MenuItem(status.name()); + +            if (status == Vehicle.Status.FREI_FUNK || status == Vehicle.Status.FREI_WACHE) { +                mi.getStyleClass().add("mi-free"); +            } else { +                mi.getStyleClass().add("mi-other"); +            } + +            mi.setOnAction( +                    event -> { +                        try { +                            vehicleService.update(data.toBuilder().status(status).build()); +                            this.updateList(); +                        } catch (InvalidVehicleException e) { +                            LOG.debug( +                                    "Validation error in createContextMenu(). (mi.setOnAction) ", +                                    e); +                            showValidationErrorAlertAndWait(e.getMessage()); +                        } catch (ServiceException e) { +                            LOG.error("Exception in createContextMenu(). (mi.setOnAction) ", e); +                            showServiceExceptionAlertAndWait( +                                    "Beim Aktualisieren der Fahrzeuge ist ein Fehler aufgetreten."); +                        } +                    }); + +            menu.getItems().add(mi); +        } + +        MenuItem abmelden = new MenuItem("abmelden"); + +        abmelden.setOnAction( +                event -> { +                    try { +                        if (data.registrations() == null) return; + +                        for (Registration registration : data.registrations()) +                            registrationService.remove(registration.id()); + +                        vehicleService.update( +                                data.toBuilder().status(Vehicle.Status.ABGEMELDET).build()); +                        this.updateList(); +                    } catch (InvalidVehicleException | InvalidRegistrationException e) { +                        LOG.debug( +                                "Validation error in createContextMenu(). (abmelden.setOnAction) ", +                                e); +                        showValidationErrorAlertAndWait(e.getMessage()); +                    } catch (ServiceException e) { +                        LOG.error("Exception in createContextMenu(). (abmelden.setOnAction) ", e); +                        showServiceExceptionAlertAndWait( +                                "Beim Aktualisieren der Fahrzeuge ist ein Fehler aufgetreten."); +                    } +                }); + +        menu.getItems().add(abmelden); +        return menu; +    } + +    @FXML +    protected void createOperationClicked() { +        LOG.debug("Button \"Erstellen\" clicked."); +        Vehicle[] vehicles = new Vehicle[chosenVehicles.size()]; +        for (int i = 0; i < chosenVehicles.size(); i++) { +            vehicles[i] = chosenVehicles.get(i); +        } +        Operation operation = +                Operation.builder() +                        .additionalInfo(txtNote.getText()) +                        .destination(txtAddress.getText()) +                        .opCode(txtCode.getText()) +                        .status(Status.ACTIVE) +                        .vehicles(Set.of(vehicles)) +                        .build(); +        try { +            operationService.add(operation); +        } catch (ServiceException e) { +            LOG.error("Exception in createOperationClicked(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Erstellen des Einsatzes ist ein Fehler aufgetreten."); +            return; +        } catch (InvalidOperationException e) { +            LOG.debug("Validation error in createOperationClicked(). ", e); +            showValidationErrorAlertAndWait(e.getMessage()); +            return; +        } +        showSuccessAlertAndWait("Der Einsatz wurde erfolgreich gespeichert."); +        updateList(); +        lblChosenVehicles.setText("keine ausgewählt"); +        txtAddress.setText(""); +        txtCode.setText(""); +        txtNote.setText(""); +        chosenVehicles = new LinkedList<>(); +    } + +    @FXML +    private void onRegistrationLinkClicked() { +        LOG.debug("Hyperlink \"Anmeldungen\" clicked."); +        openRegistrationWindow(); +    } + +    @FXML +    private void onEmployeeLinkClicked() { +        LOG.debug("Hyperlink \"Personen\" clicked."); +        openCreateNewEmployeeWindow(); +    } + +    @FXML +    private void onVehicleLinkClicked() { +        LOG.debug("Hyperlink \"Fahrzeuge\" clicked."); +        openCreateCarWindow(); +    } + +    @FXML +    private void onArchivLinkClicked() { +        LOG.debug("Hyperlink \"Archiv\" clicked."); +        archiveOperationController.update(); +        openArchivWindow(); +    } + +    private void openArchivWindow() { +        archiveOperationController.setVisible(true); +        this.setVisible(false); +    } + +    void setVisible(boolean b) { +        apInvisible.setVisible(!b); +        grdWindowContainer.setVisible(!b); + +        // if (b) updateList(); +    } + +    private void openDetailsWindow(Operation operation) { +        operationDetailsController.initOperation(operation); +        this.setVisible(false); +    } + +    private void openCreateNewEmployeeWindow() { +        this.setVisible(false); +        manageEmployeesController.setVisible(true); +    } + +    private void openCreateCarWindow() { +        this.setVisible(false); +        createCarController.setVisible(true); +        createCarController.updateVehiclePane(); +    } + +    private void openRegistrationWindow() { +        this.setVisible(false); +        registrationWindowController.setVisible(true); +    } + +    @FXML +    private void onOperationCodeChanged(KeyEvent keyEvent) { +        if (keyEvent.getCode() == KeyCode.ENTER) { +            updateList(); +        } +    } + +    static ListCell<Operation> generateOpCodeListItem() { +        return new ListCell<>() { +            @Override +            protected void updateItem(Operation item, boolean empty) { +                super.updateItem(item, empty); + +                if (empty || item == null || item.opCode() == null) { +                    setText(null); +                } else { +                    setText(item.opCode()); +                } +            } +        }; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CustomListItemController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CustomListItemController.java new file mode 100644 index 0000000..ced0c10 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/CustomListItemController.java @@ -0,0 +1,24 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import javafx.scene.Node; + +public abstract class CustomListItemController { + +    protected Node rootElement; + +    public Node getRootElement() { +        return rootElement; +    } + +    public void setSelected(boolean selected) { +        rootElement.getStyleClass().clear(); + +        if (selected) { +            rootElement.getStyleClass().add("bg-yellow"); +            rootElement.getStyleClass().add("shadowed"); +        } else { +            rootElement.getStyleClass().add("bg-white"); +            rootElement.getStyleClass().add("shadowed"); +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/DetailArchiveOperationController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/DetailArchiveOperationController.java new file mode 100644 index 0000000..a866653 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/DetailArchiveOperationController.java @@ -0,0 +1,96 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader.FXMLWrapper; +import java.io.IOException; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.layout.VBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class DetailArchiveOperationController { +    private static final Logger LOG = +            LoggerFactory.getLogger(DetailArchiveOperationController.class); + +    @FXML private VBox vBoxVehicle; +    @FXML private VBox vBoxPeople; +    private final SpringFXMLLoader fxmlLoader; + +    public DetailArchiveOperationController(SpringFXMLLoader fxmlLoader) { +        this.fxmlLoader = fxmlLoader; +    } + +    static DetailArchiveOperationController create(SpringFXMLLoader fxmlLoader) throws IOException { +        FXMLWrapper<Object, DetailArchiveOperationController> wrapper = +                fxmlLoader.loadAndWrap( +                        "/fxml/DetailArchiveOperation.fxml", +                        DetailArchiveOperationController.class); + +        Node root = (Node) wrapper.getLoadedObject(); +        DetailArchiveOperationController result = wrapper.getController(); +        result.rootElement = root; + +        return result; +    } + +    public Node getRoot() { +        return rootElement; +    } + +    private Node rootElement; + +    public void set(Vehicle vehicle, Operation operation) { +        VehiclePaneController controller; +        try { +            controller = VehiclePaneController.createVehiclePane(); +            controller.setData(vehicle, false, false); +            vBoxVehicle.getChildren().add(controller.getRootElement()); +        } catch (IOException e) { +            LOG.error("IOException in set(Vehicle). (vBoxVehicle) ", e); +            showServiceExceptionAlertAndWait( +                    "Ein interner Fehler ist aufgetreten. Bitte wenden Sie sich an den/die SystemadministratorIn."); +        } +        try { +            List<Registration> registrations = +                    Objects.requireNonNull(vehicle.registrations()) +                            .stream() +                            .filter( +                                    registration -> +                                            registration +                                                            .start() +                                                            .isBefore( +                                                                    Objects.requireNonNull( +                                                                            operation.created())) +                                                    && registration +                                                            .end() +                                                            .isAfter( +                                                                    Objects.requireNonNull( +                                                                            operation.created()))) +                            .collect(Collectors.toList()); + +            for (Registration registration : registrations) { +                Employee employee = registration.employee(); +                EmployeeListItemController employeeListItemController = +                        EmployeeListItemController.createEmployeeListItemController( +                                fxmlLoader, employee); +                vBoxPeople.getChildren().add(employeeListItemController.getRootElement()); +            } +        } catch (IOException e) { +            LOG.error("IOException in set(Vehicle). (vBoxPeople) ", e); +            showServiceExceptionAlertAndWait( +                    "Ein interner Fehler ist aufgetreten. Bitte wenden Sie sich an den/die SystemadministratorIn."); +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListController.java new file mode 100644 index 0000000..12f6bff --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListController.java @@ -0,0 +1,133 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader.FXMLWrapper; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.layout.FlowPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; + +@Controller +@Scope("prototype") +public class EmployeeListController { + +    private static final Logger LOG = LoggerFactory.getLogger(EmployeeListController.class); + +    @FXML private FlowPane flowPaneEmployeeList; + +    private Consumer<Employee> onEmployeeClicked; + +    private final SpringFXMLLoader fxmlLoader; +    private Node rootElement; +    private List<EmployeeListItemController> employeeListItemControllers; +    private Insets listItemMargins = new Insets(10, 5, 0, 5); + +    public EmployeeListController(SpringFXMLLoader fxmlLoader) { +        this.fxmlLoader = fxmlLoader; +        this.employeeListItemControllers = new ArrayList<>(); +    } + +    public void setListItemMargins(Insets value) { +        this.listItemMargins = value; +    } + +    public void setData(Set<Employee> employeeList) { +        setData(employeeList, null, null); +    } + +    public void setData(Set<Employee> employeeList, Consumer<Employee> onEmployeeClicked) { +        setData(employeeList, onEmployeeClicked, null); +    } + +    public void setData( +            Set<Employee> employeeList, +            Consumer<Employee> onEmployeeClicked, +            Consumer<EmployeeListItemController> onEmployeeListItemClicked) { + +        flowPaneEmployeeList.getChildren().clear(); +        employeeListItemControllers.clear(); +        employeeList.forEach( +                employee -> +                        addEmployeeToFlowPane( +                                employee, onEmployeeClicked, onEmployeeListItemClicked)); +    } + +    private void addEmployeeToFlowPane( +            Employee employee, +            Consumer<Employee> onEmployeeClicked, +            Consumer<EmployeeListItemController> onEmployeeListItemClicked) { + +        try { +            EmployeeListItemController controller = +                    EmployeeListItemController.createEmployeeListItemController( +                            fxmlLoader, employee); +            Node rootElement = controller.getRootElement(); +            flowPaneEmployeeList.getChildren().add(rootElement); +            employeeListItemControllers.add(controller); +            FlowPane.setMargin(rootElement, listItemMargins); +            if (onEmployeeClicked != null) { +                controller.setConsumerEmployeeClicked(onEmployeeClicked); +            } +            if (onEmployeeListItemClicked != null) { +                controller.setConsumerEmployeeListItemClicked( +                        employeeListItemController -> { +                            onEmployeeListItemClicked.accept(employeeListItemController); +                            if (this.onEmployeeClicked != null) { +                                this.onEmployeeClicked.accept( +                                        employeeListItemController.getEmployee()); +                            } +                        }); +            } +        } catch (IOException e) { +            LOG.error("IOException in addEmployeeToFlowPane. ", e); +        } +    } + +    private void setEmployeeSelected(Employee employee, boolean selected) { +        employeeListItemControllers +                .stream() +                .filter(controller -> controller.getEmployee().equals(employee)) +                .forEach(controller -> controller.setSelected(selected)); +    } + +    public void selectEmployee(Employee employee) { +        setEmployeeSelected(employee, true); +    } + +    public void deselectEmployee(Employee employee) { +        setEmployeeSelected(employee, false); +    } + +    public void deselectAllEmployees() { +        employeeListItemControllers.forEach( +                employeeListItemController -> employeeListItemController.setSelected(false)); +    } + +    public static EmployeeListController createEmployeeListController(SpringFXMLLoader loader) +            throws IOException { +        FXMLWrapper<Object, EmployeeListController> wrapper = +                loader.loadAndWrap("/fxml/employeeList.fxml", EmployeeListController.class); +        Node root = (Node) wrapper.getLoadedObject(); +        EmployeeListController controller = wrapper.getController(); +        controller.rootElement = root; +        return controller; +    } + +    public Node getRootElement() { +        return rootElement; +    } + +    public void setOnEmployeeClicked(Consumer<Employee> onEmployeeClicked) { +        this.onEmployeeClicked = onEmployeeClicked; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListItemController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListItemController.java new file mode 100644 index 0000000..d445b43 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/EmployeeListItemController.java @@ -0,0 +1,89 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader.FXMLWrapper; +import java.io.IOException; +import java.util.function.Consumer; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; + +@Controller +@Scope("prototype") +public class EmployeeListItemController extends CustomListItemController { + +    @FXML private Label lblName; +    @FXML private Label lblQualification; +    @FXML private Label lblPilot; +    @FXML private Label lblDriver; +    @FXML private ImageView imgPilot; +    @FXML private ImageView imgDriver; +    @FXML private ImageView imgQualification; + +    private Employee employee; + +    private Consumer<Employee> consumerEmployeeClicked; +    private Consumer<EmployeeListItemController> consumerEmployeeListItemClicked; + +    private static Image imageQualification = new Image("/images/Qualification.png"); +    private static Image imagePilot = new Image("/images/Pilot.png"); +    private static Image imageNotPilot = new Image("/images/NotPilot.png"); +    private static Image imageDriver = new Image("images/Driver.png"); +    private static Image imageNotDriver = new Image("images/NotDriver.png"); + +    @FXML +    private void onEmployeeClicked() { +        if (consumerEmployeeClicked != null) { +            consumerEmployeeClicked.accept(employee); +        } +        if (consumerEmployeeListItemClicked != null) { +            consumerEmployeeListItemClicked.accept(this); +        } +    } + +    private void setData(Employee employee) { +        this.employee = employee; +        lblName.setText(employee.name()); +        lblQualification.setText(employee.educationLevel().name()); +        lblPilot.setText(String.format("%s Pilot", employee.isPilot() ? "ist" : "nicht")); +        lblDriver.setText(String.format("%s Fahrer", employee.isDriver() ? "ist" : "nicht")); +        imgQualification.setImage(imageQualification); +        imgPilot.setImage(employee.isPilot() ? imagePilot : imageNotPilot); +        imgDriver.setImage(employee.isDriver() ? imageDriver : imageNotDriver); +    } + +    public static EmployeeListItemController createEmployeeListItemController( +            SpringFXMLLoader fxmlLoader, Employee employee) throws IOException { +        EmployeeListItemController controller = createEmployeeListItemController(fxmlLoader); +        controller.setData(employee); +        return controller; +    } + +    public static EmployeeListItemController createEmployeeListItemController( +            SpringFXMLLoader loader) throws IOException { +        FXMLWrapper<Object, EmployeeListItemController> wrapper = +                loader.loadAndWrap("/fxml/employeeListItem.fxml", EmployeeListItemController.class); +        Node root = (Node) wrapper.getLoadedObject(); +        EmployeeListItemController controller = wrapper.getController(); +        controller.rootElement = root; +        return controller; +    } + +    public Employee getEmployee() { +        return employee; +    } + +    public void setConsumerEmployeeClicked(Consumer<Employee> consumerEmployeeClicked) { +        this.consumerEmployeeClicked = consumerEmployeeClicked; +    } + +    public void setConsumerEmployeeListItemClicked( +            Consumer<EmployeeListItemController> consumerEmployeeListItemClicked) { +        this.consumerEmployeeListItemClicked = consumerEmployeeListItemClicked; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/FilterEmployeesController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/FilterEmployeesController.java new file mode 100644 index 0000000..a31c3e3 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/FilterEmployeesController.java @@ -0,0 +1,65 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader.FXMLWrapper; +import java.io.IOException; +import java.util.function.Consumer; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.TextField; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; +import org.springframework.stereotype.Controller; + +@Controller +@Scope("prototype") +public class FilterEmployeesController { +    private static final Logger LOG = LoggerFactory.getLogger(FilterEmployeesController.class); + +    @FXML private TextField inputFilterString; + +    private Consumer<String> consumerFilterTextChanged; +    private Runnable consumerAddEmployeeClicked; + +    private Node rootElement; + +    @FXML +    private void onAddEmployeeClicked() { +        LOG.debug("Button \"Person hinzufügen\" clicked."); +        if (consumerAddEmployeeClicked != null) { +            consumerAddEmployeeClicked.run(); +        } +    } + +    @FXML +    private void onFilterTextChanged() { +        LOG.debug("Filter text changed."); +        if (consumerFilterTextChanged != null) { +            consumerFilterTextChanged.accept(inputFilterString.getText()); +        } +    } + +    public void setOnFilterTextChangedListener(Consumer<String> callback) { +        this.consumerFilterTextChanged = callback; +    } + +    public void setOnAddEmployeeClickedListener(Runnable callback) { +        this.consumerAddEmployeeClicked = callback; +    } + +    public static FilterEmployeesController createFilterEmployeesController( +            SpringFXMLLoader fxmlLoader) throws IOException { +        FXMLWrapper<Object, FilterEmployeesController> wrapper = +                fxmlLoader.loadAndWrap( +                        "/fxml/filterEmployeesControl.fxml", FilterEmployeesController.class); +        Node root = (Node) wrapper.getLoadedObject(); +        FilterEmployeesController controller = wrapper.getController(); +        controller.rootElement = root; +        return controller; +    } + +    public Node getRootElement() { +        return rootElement; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/Helper.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/Helper.java new file mode 100644 index 0000000..f120eb6 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/Helper.java @@ -0,0 +1,34 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ButtonType; + +public class Helper { + +    static final String ALERT_TITLE_VALIDATION_ERROR = "Validierungsfehler"; +    static final String ALERT_TITLE_SERVICE_EXCEPTION = "Fehler"; +    static final String ALERT_TITLE_SUCCESS = "Erfolg"; + +    private Helper() {} // SonarLint insisted to create a private constructor to hide the public one + +    static void showValidationErrorAlertAndWait(String message) { +        showAlertWithOkButtonAndWait(AlertType.ERROR, ALERT_TITLE_VALIDATION_ERROR, message); +    } + +    static void showServiceExceptionAlertAndWait(String message) { +        showAlertWithOkButtonAndWait(AlertType.ERROR, ALERT_TITLE_SERVICE_EXCEPTION, message); +    } + +    static void showSuccessAlertAndWait(String message) { +        showAlertWithOkButtonAndWait(AlertType.INFORMATION, ALERT_TITLE_SUCCESS, message); +    } + +    static void showAlertWithOkButtonAndWait( +            AlertType alertType, String headerText, String contentText) { +        Alert alert = new Alert(alertType, contentText, ButtonType.OK); +        alert.setTitle(headerText); +        alert.setHeaderText(headerText); +        alert.showAndWait(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ManageEmployeesController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ManageEmployeesController.java new file mode 100644 index 0000000..fa228de --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/ManageEmployeesController.java @@ -0,0 +1,120 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.EmployeeService; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import java.io.IOException; +import java.util.HashSet; +import java.util.stream.Collectors; +import javafx.fxml.FXML; +import javafx.scene.layout.AnchorPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class ManageEmployeesController { + +    private static final Logger LOG = LoggerFactory.getLogger(ManageEmployeesController.class); +    @FXML private AnchorPane listEmployeesAP; +    @FXML private AnchorPane containerHeader; +    @FXML private EmployeeListController employeeListController; + +    private final EmployeeService employeeService; +    private final SpringFXMLLoader fxmlLoader; + +    private final CreateOperationController createOperationController; + +    public ManageEmployeesController( +            EmployeeService employeeService, +            SpringFXMLLoader fxmlLoader, +            CreateOperationController createOperationController) { +        this.employeeService = employeeService; +        this.fxmlLoader = fxmlLoader; +        this.createOperationController = createOperationController; +    } + +    @FXML +    private void initialize() { +        openFilter(); +    } + +    private void openFilter() { +        try { +            FilterEmployeesController filterEmployeesController = +                    FilterEmployeesController.createFilterEmployeesController(fxmlLoader); +            containerHeader.getChildren().clear(); +            containerHeader.getChildren().add(filterEmployeesController.getRootElement()); +            filterEmployeesController.setOnFilterTextChangedListener(this::updateEmployeeList); +            filterEmployeesController.setOnAddEmployeeClickedListener(this::openAddEmployee); +            updateEmployeeList(); + +        } catch (IOException e) { +            LOG.error("IOException in openFilter().", e); +        } +    } + +    private void openAddEmployee() { +        employeeListController.deselectAllEmployees(); +        openEmployee(null); +    } + +    private void openEditEmployee(Employee employee) { +        employeeListController.deselectAllEmployees(); +        employeeListController.selectEmployee(employee); +        openEmployee(employee); +    } + +    private void openEmployee(Employee employee) { +        try { +            CreateNewEmployeeController createNewEmployeeController = +                    employee == null +                            ? CreateNewEmployeeController.createCreateNewEmployeeController( +                                    fxmlLoader) +                            : CreateNewEmployeeController.createCreateNewEmployeeController( +                                    fxmlLoader, employee); +            containerHeader.getChildren().clear(); +            containerHeader.getChildren().add(createNewEmployeeController.getRootElement()); +            createNewEmployeeController.setConsumerCancelClicked(this::openFilter); +            createNewEmployeeController.setConsumerCreateClicked(this::openFilter); +        } catch (IOException e) { +            LOG.error("IOException in openEmployee(). ", e); +        } +    } + +    private void updateEmployeeList() { +        updateEmployeeList(""); +    } + +    private void updateEmployeeList(String searchString) { + +        try { +            employeeListController.setData( +                    employeeService +                            .list() +                            .stream() +                            .filter( +                                    employee -> +                                            searchString.trim().isEmpty() +                                                    || employee.name() +                                                            .toLowerCase() +                                                            .contains(searchString.toLowerCase())) +                            .collect(Collectors.toCollection(HashSet::new)), +                    this::openEditEmployee); + +        } catch (ServiceException e) { +            LOG.error("ServiceException in updateEmployeeList(). ", e); +        } +    } + +    public void setVisible(boolean b) { +        listEmployeesAP.setVisible(b); +    } + +    public void backToMain() { +        LOG.debug("Hyperlink \"Zurück\" clicked."); +        this.setVisible(false); +        createOperationController.setVisible(true); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationDetailsController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationDetailsController.java new file mode 100644 index 0000000..daeaedd --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationDetailsController.java @@ -0,0 +1,194 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showAlertWithOkButtonAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showSuccessAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showValidationErrorAlertAndWait; + +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.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +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.service.OperationService; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.VehicleService; +import java.io.IOException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.fxml.FXML; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ListView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.FlowPane; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class OperationDetailsController { +    private static final Logger LOG = LoggerFactory.getLogger(OperationDetailsController.class); + +    public Operation operation; +    private final OperationService operationService; +    private final VehicleService vehicleService; +    private final CreateOperationController createOperationController; +    @FXML private FlowPane fpVehicles; +    @FXML private FlowPane fpAdditional; +    @FXML private ListView<Operation> lvActiveOperations; +    @FXML private Label lblChosenVehicles; +    @FXML private Button btnCloseOperation; +    @FXML private Button btnCancelOperation; +    @FXML private Label lblCode, lblAdditionalInfo, lblAddress; +    @FXML private AnchorPane operationDetailsAP; + +    public OperationDetailsController( +            OperationService operationService, +            VehicleService vehicleService, +            CreateOperationController createOperationController) { +        this.operationService = operationService; +        this.vehicleService = vehicleService; +        this.createOperationController = createOperationController; +    } + +    @FXML +    private void initialize() { +        lvActiveOperations.setCellFactory( +                param -> CreateOperationController.generateOpCodeListItem()); +        lvActiveOperations.setOnMouseClicked( +                event -> { +                    if (event.getClickCount() == 2) { +                        if (lvActiveOperations.getSelectionModel().getSelectedItem() == null) { +                            return; +                        } +                        initOperation(lvActiveOperations.getSelectionModel().getSelectedItem()); +                    } +                }); +    } + +    private void updateFlowPane() { +        try { +            fpVehicles.getChildren().clear(); +            for (Vehicle vehicle : operation.vehicles()) { +                VehiclePaneController controller = VehiclePaneController.createVehiclePane(); +                controller.setData(vehicle, true, false); +                fpVehicles.getChildren().add(controller.getRootElement()); +            } + +            fpAdditional.getChildren().clear(); +            for (Vehicle vehicle : operationService.rankVehicles(operation.opCode())) { +                if (operation.vehicles().contains(vehicle)) continue; + +                VehiclePaneController controller = VehiclePaneController.createVehiclePane(); +                controller.setData(vehicle, true, true); +                controller.getBtnRequest().setOnAction(e -> requestVehicleClicked(controller)); +                fpAdditional.getChildren().add(controller.getRootElement()); +            } +        } catch (IOException | ServiceException e) { +            LOG.error("Error while updating list.", e); +            showServiceExceptionAlertAndWait("Error while updating list."); +        } catch (InvalidOperationException e) { +            LOG.debug("Validation for Operation failed"); +            showValidationErrorAlertAndWait(e.getMessage()); +        } +    } + +    void initOperation(Operation operation) { +        fillActiveList(); +        this.operation = operation; +        lblCode.setText(operation.opCode()); +        Collection<String> vehicleNames = +                operation.vehicles().stream().map(Vehicle::name).collect(Collectors.toList()); +        String result = String.join(", ", vehicleNames); +        lblChosenVehicles.setText(result.toString()); +        lblAdditionalInfo.setText(operation.additionalInfo()); +        lblAddress.setText(operation.destination()); +        updateFlowPane(); +        operationDetailsAP.setVisible(true); +    } + +    private void fillActiveList() { +        try { +            lvActiveOperations.setItems( +                    FXCollections.observableArrayList( +                            operationService.list(EnumSet.of(Status.ACTIVE)))); +        } catch (ServiceException e) { +            LOG.error("ServiceException in fillActiveList(). ", e); +            showServiceExceptionAlertAndWait(e.getMessage()); +        } +    } + +    @FXML +    public void closeOperationClicked() { +        LOG.debug("Button \"Abschließen\" clicked."); +        try { +            operationService.complete(operation.id(), Status.COMPLETED); +        } catch (InvalidOperationException e) { +            LOG.debug("Validation error in closeOperationClicked(). ", e); +            showAlertWithOkButtonAndWait(AlertType.ERROR, "Validierungsfehler", e.getMessage()); +            return; +        } catch (ServiceException e) { +            LOG.error("Exception in closeOperationClicked(). ", e); +            showServiceExceptionAlertAndWait(e.getMessage()); +            return; +        } +        showSuccessAlertAndWait("Der Einsatz wurde erfolgreich aktualisiert"); +        createOperationController.updateList(); +        closeWindow(); +    } + +    public void cancelOperationClicked() { +        LOG.debug("Button \"Stornieren\" clicked."); +        try { +            operationService.complete(operation.id(), Status.CANCELLED); +        } catch (InvalidOperationException e) { +            LOG.debug("Validation error in cancelOperationClicked(). ", e); +            showValidationErrorAlertAndWait(e.getMessage()); +            return; +        } catch (ServiceException e) { +            LOG.error("Exception in cancelOperationClicked(). ", e); +            showServiceExceptionAlertAndWait(e.getMessage()); +            return; +        } +        showSuccessAlertAndWait("Der Einsatz wurde erfolgreich aktualisiert"); +        createOperationController.updateList(); +        closeWindow(); +    } + +    private void requestVehicleClicked(VehiclePaneController v) { +        LOG.debug("Button \"Nachfordern\" clicked."); + +        Vehicle vehicle = null; + +        try { +            vehicle = v.getData(); +            if (vehicle == null) return; + +            operationService.requestVehicles(operation.id(), Set.of(vehicle.id())); +        } catch (ServiceException e) { +            LOG.error("ServiceException in requestVehicleClicked()", e); +            showServiceExceptionAlertAndWait(e.getMessage()); +            return; +        } catch (InvalidVehicleException e) { +            LOG.debug("Validation of Vehicle failed"); +            showValidationErrorAlertAndWait(e.getMessage()); +        } catch (InvalidOperationException e) { +            LOG.debug("Validation of Operation failed"); +            showValidationErrorAlertAndWait(e.getMessage()); +        } +        showSuccessAlertAndWait("Das Fahrzeug wurde erfolgreich angefordert"); +        operation.vehicles().add(vehicle); +        updateFlowPane(); +    } + +    public void closeWindow() { +        LOG.debug("Hyperlink \"Zurück\" clicked."); +        operationDetailsAP.setVisible(false); +        this.createOperationController.setVisible(true); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationInArchiveController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationInArchiveController.java new file mode 100644 index 0000000..17f0f55 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/OperationInArchiveController.java @@ -0,0 +1,65 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.util.Collection; +import java.util.Objects; +import java.util.stream.Collectors; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.text.Text; + +public class OperationInArchiveController { + +    @FXML private Text txtAddress; +    @FXML private Text txtVehicles; +    @FXML private Text txtDate; +    @FXML private Text txtOpCode; + +    static OperationInArchiveController create() throws IOException { +        FXMLLoader fxmlLoader = +                new FXMLLoader( +                        OperationInArchiveController.class.getResource( +                                "/fxml/OperationInArchive.fxml")); +        Node root = fxmlLoader.load(); +        OperationInArchiveController result = fxmlLoader.getController(); +        result.rootElement = root; + +        return result; +    } + +    public Node getRoot() { +        return rootElement; +    } + +    private Node rootElement; + +    public void set(Operation operation) { +        txtAddress.setText(operation.destination()); +        String date = "am "; +        if (operation.created() != null) { +            LocalDateTime myDateTime = +                    LocalDateTime.ofInstant( +                            Objects.requireNonNull(operation.created()), ZoneOffset.UTC); +            date += +                    myDateTime.getDayOfMonth() +                            + "." +                            + myDateTime.getMonth().getValue() +                            + "." +                            + myDateTime.getYear(); +            txtDate.setText(date); +        } else { +            txtDate.setText("---"); +        } +        txtOpCode.setText(operation.opCode()); +        Collection<String> elements = +                operation.vehicles().stream().map(Vehicle::name).collect(Collectors.toList()); +        String result = String.join(", ", elements); + +        txtVehicles.setText(result); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/RegistrationWindowController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/RegistrationWindowController.java new file mode 100644 index 0000000..c445a12 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/RegistrationWindowController.java @@ -0,0 +1,289 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showServiceExceptionAlertAndWait; +import static at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller.Helper.showValidationErrorAlertAndWait; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidRegistrationException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.EmployeeService; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.RegistrationService; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service.VehicleService; +import at.ac.tuwien.sepm.assignment.groupphase.util.SpringFXMLLoader; +import java.io.IOException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.OffsetDateTime; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Insets; +import javafx.scene.Node; +import javafx.scene.control.ChoiceBox; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyEvent; +import javafx.scene.input.MouseButton; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Controller; + +@Controller +public class RegistrationWindowController { + +    private static final Logger LOG = LoggerFactory.getLogger(RegistrationWindowController.class); + +    private final EmployeeService employeeService; +    private final VehicleService vehicleService; +    private final RegistrationService registrationService; +    private final CreateOperationController createOperationController; +    private final SpringFXMLLoader fxmlLoader; + +    @FXML private GridPane root; +    @FXML private VBox vbVehicles; +    @FXML private ScrollPane listEmployee; +    @FXML private ChoiceBox<Integer> cbStart; +    @FXML private ChoiceBox<Integer> cbEnd; +    @FXML private Label lVehicles; +    @FXML private Label lEmployees; +    @FXML private TextField tfVehicleSearch; +    @FXML private TextField tfEmployeeSearch; +    private EmployeeListController employeeListController; + +    private Vehicle chosenVehicle; +    private List<Employee> chosenEmployees = new LinkedList<>(); + +    public RegistrationWindowController( +            EmployeeService employeeService, +            VehicleService vehicleService, +            CreateOperationController createOperationController, +            RegistrationService registrationService, +            SpringFXMLLoader fxmlLoader) { +        this.employeeService = employeeService; +        this.vehicleService = vehicleService; +        this.createOperationController = createOperationController; +        this.registrationService = registrationService; +        this.fxmlLoader = fxmlLoader; +    } + +    @FXML +    private void initialize() throws IOException { +        employeeListController = EmployeeListController.createEmployeeListController(fxmlLoader); +        employeeListController.setListItemMargins(new Insets(10, 6, 0, 6)); +        //        listEmployee. .getChildren().add(employeeListController.getRootElement()); +        Node emplList = employeeListController.getRootElement(); +        //        emplList.(360); +        listEmployee.setContent(emplList); + +        ObservableList<Integer> hours = +                FXCollections.observableArrayList( +                        0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, +                        21, 22, 23); +        cbStart.setItems(hours); +        cbEnd.setItems(hours); +        setDefaultTime(); +        // reset(); +    } + +    private void setDefaultTime() { +        cbStart.setValue(LocalDateTime.now().getHour()); +        cbEnd.setValue((LocalDateTime.now().getHour() + 4) % 24); +    } + +    private void updateEmplList() { +        employeeListController.deselectAllEmployees(); + +        try { +            Set<Employee> employees = +                    employeeService +                            .list() +                            .stream() +                            .filter( +                                    e -> +                                            e.name() +                                                    .toLowerCase() +                                                    .contains( +                                                            tfEmployeeSearch +                                                                    .getText() +                                                                    .toLowerCase())) +                            .collect(Collectors.toCollection(HashSet::new)); +            employeeListController.setData( +                    employees, +                    selection -> { +                        if (selection == null) { +                            return; +                        } else if (chosenEmployees.contains(selection)) { +                            chosenEmployees.remove(selection); +                        } else { +                            chosenEmployees.add(selection); +                        } + +                        StringBuilder text = new StringBuilder(); +                        boolean first = true; +                        for (Employee employee : chosenEmployees) { +                            if (!first) { +                                text.append(", "); +                            } +                            text.append(employee.name()); +                            first = false; +                        } +                        lEmployees.setText(text.toString()); +                    }, +                    contr -> contr.setSelected(chosenEmployees.contains(contr.getEmployee()))); + +            employees.forEach( +                    e -> { +                        if (chosenEmployees.contains(e)) employeeListController.selectEmployee(e); +                    }); +        } catch (ServiceException e) { +            LOG.error("ServiceException in updateEmplList(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Auflisten des Personals ist ein Fehler aufgetreten."); +        } +    } + +    private void updateVehList() { +        vbVehicles.getChildren().clear(); + +        try { +            Set<Vehicle> vehicles = vehicleService.list(EnumSet.of(Status.ABGEMELDET)); + +            boolean anyMatch = false; + +            for (Vehicle vehicle : vehicles) { +                if (!vehicle.name().toLowerCase().contains(tfVehicleSearch.getText().toLowerCase())) +                    continue; + +                anyMatch = true; + +                VehiclePaneController vp = VehiclePaneController.createVehiclePane(); +                vp.setData(vehicle, false, false); +                vbVehicles.getChildren().add(vp.getRootElement()); + +                vp.getRootElement() +                        .setOnMouseClicked( +                                event -> { +                                    if (event.getButton() == MouseButton.PRIMARY) { +                                        chosenVehicle = vehicle; +                                        lVehicles.setText(chosenVehicle.name()); +                                        updateVehList(); +                                    } +                                }); +                if (chosenVehicle != null && chosenVehicle.id() == vehicle.id()) +                    vp.setSelected(true); +            } + +            if (!anyMatch) { +                // Kind of ugly, but best way to get the size of a VehiclePane +                VehiclePaneController vp = VehiclePaneController.createVehiclePane(); +                vp.getRootElement().setVisible(false); +                vbVehicles.getChildren().add(vp.getRootElement()); +            } +        } catch (ServiceException e) { +            LOG.error("ServiceException in updateVehList(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Auflisten der Fahrzeuge ist ein Fehler aufgetreten"); +        } catch (IOException e) { +            LOG.error("IOException in updateVehList(). ", e); +            showServiceExceptionAlertAndWait("Beim Laden der Fahrzeuge ist ein Fehler aufgetreten"); +        } +    } + +    public void cancel() { +        LOG.debug("Hyperlink \"schließen\" clicked"); +        this.setVisible(false); +        createOperationController.setVisible(true); +    } + +    private void reset() { +        chosenEmployees.clear(); +        chosenVehicle = null; +        tfEmployeeSearch.setText(""); +        tfVehicleSearch.setText(""); +        lEmployees.setText("-"); +        lVehicles.setText("-"); +        updateVehList(); +        updateEmplList(); +        setDefaultTime(); +    } + +    public void create() { +        LOG.debug("Button \"ERSTELLEN\" clicked"); + +        Set<Registration> registrations = new HashSet<>(); +        try { +            if (chosenVehicle == null) { +                throw new InvalidVehicleException("no Vehicle"); +            } + +            LocalDateTime startDate = +                    LocalDateTime.of( +                            LocalDate.now(), +                            LocalTime.of( +                                    cbStart.getValue(), +                                    LocalDateTime.now().getMinute(), +                                    LocalDateTime.now().getSecond())); + +            LocalDateTime endDate = +                    LocalDateTime.of( +                            LocalDate.now() +                                    .plusDays(cbStart.getValue() >= cbEnd.getValue() ? 1 : 0), +                            LocalTime.of(cbEnd.getValue(), 0)); + +            for (Employee employee : chosenEmployees) { +                registrations.add( +                        Registration.builder() +                                .id(chosenVehicle.id()) +                                .employee(employee) +                                .start(startDate.toInstant(OffsetDateTime.now().getOffset())) +                                .end(endDate.toInstant(OffsetDateTime.now().getOffset())) +                                .build()); +            } + +            registrationService.add(chosenVehicle.id(), registrations); +            chosenEmployees.clear(); +            // ((Stage) lVehicles.getScene().getWindow()).close(); +            this.setVisible(false); +            createOperationController.setVisible(true); +            createOperationController.updateList(); +            // reset(); +        } catch (InvalidVehicleException e) { +            LOG.debug("Validation of Vehicle in Registration failed."); +            showValidationErrorAlertAndWait(e.getMessage()); +        } catch (ServiceException e) { +            LOG.error("ServiceException in create(). ", e); +            showServiceExceptionAlertAndWait( +                    "Beim Erstellen der Anmeldung ist ein Fehler aufgetreten."); +        } catch (InvalidRegistrationException e) { +            LOG.debug("Validation of Registration failed."); +            showValidationErrorAlertAndWait(e.getMessage()); +        } +    } + +    public void setVisible(boolean b) { +        if (b) reset(); +        root.setVisible(b); +    } + +    public void tfVehicleSearch_TextChanged(KeyEvent keyEvent) { +        updateVehList(); +    } + +    public void tfEmployeeSearch_TextChanged(KeyEvent keyEvent) { +        updateEmplList(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/VehiclePaneController.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/VehiclePaneController.java new file mode 100644 index 0000000..66b45d2 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/controller/VehiclePaneController.java @@ -0,0 +1,118 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.controller; + +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee.EducationLevel; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import java.io.IOException; +import java.time.Instant; +import java.util.List; +import java.util.Optional; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.text.Text; + +public class VehiclePaneController extends CustomListItemController { + +    public static VehiclePaneController createVehiclePane() throws IOException { +        FXMLLoader fxmlLoader = +                new FXMLLoader(VehiclePaneController.class.getResource("/fxml/vehiclePane.fxml")); +        Node root = fxmlLoader.load(); +        VehiclePaneController result = fxmlLoader.getController(); +        result.rootElement = root; + +        return result; +    } + +    @FXML private Label txtStatus; +    @FXML private Text txtType; +    @FXML private Text txtNumber; +    @FXML private ImageView ivNEF; +    @FXML private Text txtNEF; +    @FXML private ImageView ivQualification; +    @FXML private Text txtQualification; +    @FXML private Text txtRooftype; +    @FXML private Button btnRequest; + +    private Vehicle data; + +    public Vehicle getData() { +        return data; +    } + +    /** +     * * Set the displayed data of this VehiclePane. +     * +     * @param vehicle The data to display. +     * @param showStatusInfo If true, the highest qualification of the vehicle's active registration +     *     and the vehicle's status will be shown. +     */ +    public void setData(Vehicle vehicle, boolean showStatusInfo, boolean showRequestVehicle) { +        txtType.setText(vehicle.type().name()); +        String constrType = vehicle.constructionType().name(); +        txtRooftype.setText( +                constrType.substring(0, 1).toUpperCase() + constrType.substring(1).toLowerCase()); +        txtNumber.setText("-" + vehicle.id()); +        if (vehicle.hasNef()) { +            ivNEF.setImage(new Image("images/NEF.png")); +            txtNEF.setText("hat NEF-Halterung"); +        } else { +            ivNEF.setImage(new Image("images/NotNEF.png")); +            txtNEF.setText("keine NEF-Halterung"); +        } + +        if (showRequestVehicle) { +            btnRequest.setVisible(true); +            btnRequest.setManaged(true); +        } else { +            btnRequest.setVisible(false); +            btnRequest.setManaged(false); +        } + +        if (showStatusInfo) { +            txtStatus.setText(vehicle.status().name()); +            if (vehicle.status() == Status.FREI_FUNK || vehicle.status() == Status.FREI_WACHE) { +                txtStatus.getStyleClass().add("bg-status-green"); +            } else { +                txtStatus.getStyleClass().add("bg-status-orange"); +            } + +            Instant now = Instant.now(); +            List<Registration> regs = vehicle.registrations(); + +            if (regs == null) { +                return; +            } + +            Optional<EducationLevel> edu = +                    regs.stream() +                            .filter(reg -> reg.start().isBefore(now) && reg.end().isAfter(now)) +                            .map(reg -> reg.employee().educationLevel()) +                            .max(EducationLevel::compareTo); + +            if (!edu.isPresent()) { +                return; +            } + +            txtQualification.setText(edu.get().name()); +        } else { +            txtQualification.setVisible(false); +            txtQualification.setManaged(false); +            ivQualification.setVisible(false); +            ivQualification.setManaged(false); + +            txtStatus.setVisible(false); +        } + +        this.data = vehicle; +    } + +    public Button getBtnRequest() { +        return btnRequest; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDAO.java new file mode 100644 index 0000000..675e951 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDAO.java @@ -0,0 +1,44 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import java.util.Set; + +public interface EmployeeDAO { + +    /** +     * Persist the given employee. +     * +     * @param employee that should be stored +     * @return the id that was assigned +     * @throws PersistenceException if the employee could not be persisted +     */ +    long add(Employee employee) throws PersistenceException; + +    /** +     * Update the given employee. +     * +     * @param employee that should be updated +     * @throws ElementNotFoundException if no employee with the given id exists +     * @throws PersistenceException if the employee could not be updated +     */ +    void update(Employee employee) throws ElementNotFoundException, PersistenceException; + +    /** +     * Get all stored employees. +     * +     * @return list containing all stored employees +     * @throws PersistenceException if loading the stored employees failed +     */ +    Set<Employee> list() throws PersistenceException; + +    /** +     * Remove employee with the given id from the store. +     * +     * @param id of the employee that should be removed +     * @throws ElementNotFoundException if no employee with the given id exists +     * @throws PersistenceException if the employee could not be removed +     */ +    void remove(long id) throws ElementNotFoundException, PersistenceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDatabaseDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDatabaseDAO.java new file mode 100644 index 0000000..32dd6d2 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/EmployeeDatabaseDAO.java @@ -0,0 +1,144 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee.EducationLevel; +import at.ac.tuwien.sepm.assignment.groupphase.util.JDBCConnectionManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.LocalDate; +import java.util.HashSet; +import java.util.Set; +import org.springframework.stereotype.Repository; + +@Repository +public class EmployeeDatabaseDAO implements EmployeeDAO { + +    private JDBCConnectionManager jdbcConnectionManager; + +    public EmployeeDatabaseDAO(JDBCConnectionManager jdbcConnectionManager) { +        this.jdbcConnectionManager = jdbcConnectionManager; +    } + +    private long createEmployeeVersion(Connection con, Employee e) +            throws PersistenceException, SQLException { +        String sql = +                "INSERT INTO EmployeeVersion(name, birthday, educationLevel, isDriver, isPilot) " +                        + "VALUES(?, ?, ?, ?, ?)"; + +        try (PreparedStatement pstmt = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { +            pstmt.setString(1, e.name()); +            pstmt.setObject(2, e.birthday()); +            pstmt.setInt(3, e.educationLevel().ordinal()); +            pstmt.setBoolean(4, e.isDriver()); +            pstmt.setBoolean(5, e.isPilot()); +            pstmt.executeUpdate(); + +            try (ResultSet rs = pstmt.getGeneratedKeys()) { +                if (!rs.next()) throw new PersistenceException("Failed to insert EmployeeVersion"); + +                return rs.getLong(1); +            } +        } +    } + +    @Override +    public long add(Employee employee) throws PersistenceException { +        String sql = "INSERT INTO Employee(version) VALUES(?)"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); + +            long versionId = createEmployeeVersion(con, employee); + +            try (PreparedStatement pstmt = +                    con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { +                pstmt.setLong(1, versionId); +                pstmt.executeUpdate(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    if (!rs.next()) { +                        con.rollback(); +                        throw new PersistenceException("Failed to insert Employee"); +                    } + +                    con.commit(); +                    return rs.getLong(1); +                } +            } +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void update(Employee employee) throws ElementNotFoundException, PersistenceException { +        String sql = "UPDATE Employee SET version = ? WHERE id = ?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); + +            long versionId = createEmployeeVersion(con, employee); + +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setLong(1, versionId); +                pstmt.setLong(2, employee.id()); + +                if (pstmt.executeUpdate() != 1) { +                    con.rollback(); +                    throw new ElementNotFoundException("No such employeeId exists"); +                } +            } + +            con.commit(); +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public Set<Employee> list() throws PersistenceException { +        String sql = +                "SELECT emp.id, v.name, v.birthday, v.educationLevel, v.isDriver, v.isPilot " +                        + "FROM employee emp " +                        + "JOIN EmployeeVersion v ON v.id = emp.version"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            Set<Employee> employees = new HashSet<>(); + +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                try (ResultSet rs = pstmt.executeQuery()) { +                    while (rs.next()) { +                        employees.add( +                                Employee.builder() +                                        .id(rs.getLong(1)) +                                        .name(rs.getString(2)) +                                        .birthday(rs.getObject(3, LocalDate.class)) +                                        .educationLevel(EducationLevel.valueOf(rs.getString(4))) +                                        .isDriver(rs.getBoolean(5)) +                                        .isPilot(rs.getBoolean(6)) +                                        .build()); +                    } +                } +            } + +            return employees; +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void remove(long id) throws ElementNotFoundException, PersistenceException { +        throw new UnsupportedOperationException(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDAO.java new file mode 100644 index 0000000..e496898 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDAO.java @@ -0,0 +1,48 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation.Status; +import java.util.EnumSet; +import java.util.Set; + +public interface OperationDAO { + +    /** +     * Persist the given operation. +     * +     * @param operation that should be stored +     * @return the id that was assigned +     * @throws PersistenceException if the operation could not be persisted +     */ +    long add(Operation operation) throws PersistenceException; + +    /** +     * Update the given operation. +     * +     * @param operation that should be updated +     * @throws ElementNotFoundException if no operation with the given id exists +     * @throws PersistenceException if the operation could not be updated +     */ +    void update(Operation operation) throws ElementNotFoundException, PersistenceException; + +    /** +     * Returns the operation with the given id. +     * +     * @param operationId id of the operation that should be returned +     * @return operation with the given id +     * @throws ElementNotFoundException if no operation with the given id exists +     * @throws PersistenceException if the operation could not be loaded +     */ +    Operation get(long operationId) throws ElementNotFoundException, PersistenceException; + +    /** +     * Get all stored operations with matching status. +     * +     * @param statuses set containing all statuses that should be matched +     * @return list containing all matched operations +     * @throws PersistenceException if loading the stored operations failed +     */ +    Set<Operation> list(EnumSet<Status> statuses) throws PersistenceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDatabaseDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDatabaseDAO.java new file mode 100644 index 0000000..238a2a8 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/OperationDatabaseDAO.java @@ -0,0 +1,253 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +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.ConstructionType; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.VehicleType; +import at.ac.tuwien.sepm.assignment.groupphase.util.JDBCConnectionManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Repository; + +@Repository +public class OperationDatabaseDAO implements OperationDAO { + +    private JDBCConnectionManager jdbcConnectionManager; +    private VehicleDAO vehicleDAO; +    private RegistrationDatabaseDAO registrationDAO; + +    public OperationDatabaseDAO( +            JDBCConnectionManager jdbcConnectionManager, +            VehicleDAO vehicleDAO, +            RegistrationDatabaseDAO registrationDAO) { +        this.jdbcConnectionManager = jdbcConnectionManager; +        this.vehicleDAO = vehicleDAO; +        this.registrationDAO = registrationDAO; +    } + +    @Override +    public long add(@NonNull Operation o) throws PersistenceException { +        String sql = +                "INSERT INTO Operation(opCode, severity, created, destination, additionalInfo," +                        + "            status) VALUES (?, ?, ?, ?, ?, ?)"; +        long operationId; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setString(1, o.opCode()); +                pstmt.setInt(2, o.severity().ordinal()); +                pstmt.setObject(3, OffsetDateTime.ofInstant(o.created(), ZoneId.systemDefault())); +                pstmt.setString(4, o.destination()); +                pstmt.setString(5, o.additionalInfo()); +                pstmt.setInt(6, o.status().ordinal()); +                pstmt.executeUpdate(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    if (!rs.next()) throw new PersistenceException("Failed to persist operation"); + +                    operationId = rs.getLong(1); +                } +            } + +            createVehicleOperation(con, operationId, o.vehicles()); +            con.commit(); +            return operationId; +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void update(@NonNull Operation o) throws ElementNotFoundException, PersistenceException { +        // Note this will, by design, not update created +        String sql = +                "UPDATE Operation SET opCode = ?, severity = ?, destination = ?," +                        + " additionalInfo = ?, status = ? WHERE id = ?"; +        String sql2 = "DELETE FROM VehicleOperation WHERE operationId = ?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setString(1, o.opCode()); +                pstmt.setInt(2, o.severity().ordinal()); +                pstmt.setString(3, o.destination()); +                pstmt.setString(4, o.additionalInfo()); +                pstmt.setInt(5, o.status().ordinal()); +                pstmt.setLong(6, o.id()); + +                if (pstmt.executeUpdate() != 1) +                    throw new ElementNotFoundException("No such operationId exists"); +            } + +            try (PreparedStatement pstmt = con.prepareStatement(sql2)) { +                pstmt.setLong(1, o.id()); +                pstmt.executeUpdate(); +            } + +            createVehicleOperation(con, o.id(), o.vehicles()); +            con.commit(); +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    private void createVehicleOperation(Connection con, long operationId, Set<Vehicle> vehicles) +            throws SQLException { +        String sql = +                "INSERT INTO VehicleOperation(vehicleId, operationId)" +                        + " SELECT version, ? FROM Vehicle WHERE id = ?"; +        String sqlUpdateVehicleStatus = +                "UPDATE Vehicle SET status = 'ZUM_BERUFUNGSORT' WHERE id = ?"; + +        try (PreparedStatement pstmt = con.prepareStatement(sql); +                PreparedStatement stmtUpdateVehicleStatus = +                        con.prepareStatement(sqlUpdateVehicleStatus)) { +            pstmt.setLong(1, operationId); + +            for (long id : (Iterable<Long>) vehicles.stream().map(Vehicle::id)::iterator) { +                pstmt.setLong(2, id); +                stmtUpdateVehicleStatus.setLong(1, id); +                pstmt.addBatch(); +                stmtUpdateVehicleStatus.addBatch(); +            } + +            pstmt.executeBatch(); +            stmtUpdateVehicleStatus.executeBatch(); +        } +    } + +    @Override +    public Operation get(long operationId) throws ElementNotFoundException, PersistenceException { +        String sql = "Select * from operation where id = ?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setLong(1, operationId); +                pstmt.execute(); + +                try (ResultSet rs = pstmt.getResultSet()) { +                    if (!rs.next()) +                        throw new ElementNotFoundException("No such element could be found"); + +                    return operationFromRS(rs); +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    @Override +    public Set<Operation> list(EnumSet<Status> statuses) throws PersistenceException { +        // This hack exists because H2 currently has a bug that prevents IN (?) with an array of +        // ids, i.e. pstmt.setArray(1, con.createArrayOf("INT", intarray) from working. See +        // commented code below. + +        // SELECT * FROM Operation WHERE status IN ('COMPLETED', 'CANCELLED') is BUGGED on H2! +        // for this reason we use the ordinal values instead +        String str = +                statuses.stream() +                        .map(e -> Integer.toString(e.ordinal())) +                        .collect(Collectors.joining(",")); + +        String sql = "SELECT * FROM Operation WHERE status IN (" + str + ")"; +        Set<Operation> operations = new HashSet<>(); + +        try { +            Connection con = jdbcConnectionManager.getConnection(); + +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                // Object[] arr = statuses.stream().map(Enum::ordinal).toArray(); +                // pstmt.setArray(1, con.createArrayOf("INT", arr)); + +                try (ResultSet rs = pstmt.executeQuery()) { +                    while (rs.next()) operations.add(operationFromRS(rs)); +                } +            } + +            return operations; +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    private Operation operationFromRS(ResultSet rs) throws PersistenceException, SQLException { +        Long operationId = rs.getLong("id"); + +        return Operation.builder() +                .id(operationId) +                .opCode(rs.getString("opCode")) +                .severity(Severity.valueOf(rs.getString("severity"))) +                .status(Status.valueOf(rs.getString("status"))) +                .vehicles(getVehiclesFromOperationId(operationId)) +                .created((rs.getObject("created", OffsetDateTime.class)).toInstant()) +                .destination(rs.getString("destination")) +                .additionalInfo(rs.getString("additionalInfo")) +                .build(); +    } + +    private Set<Vehicle> getVehiclesFromOperationId(long operationId) throws PersistenceException { +        /*String sql = +        "SELECT id FROM Vehicle WHERE version IN" +                + " (SELECT vehicleId FROM VehicleOperation WHERE operationId = ?)";*/ +        String sql = +                "SELECT vv.* FROM VehicleOperation vo JOIN VehicleVersion vv ON vv.id = vo.vehicleId WHERE operationId = ?"; + +        Set<Vehicle> vehicles = new HashSet<>(); + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setLong(1, operationId); +                pstmt.execute(); + +                try (ResultSet rs = pstmt.getResultSet()) { +                    while (rs.next()) { +                        vehicles.add(vehicleFromRS(rs)); +                    } +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } catch (ElementNotFoundException e) { +            throw new PersistenceException("VehicleOperation contained nonexistent vehicle", e); +        } + +        return vehicles; +    } + +    private Vehicle vehicleFromRS(ResultSet rs) +            throws SQLException, PersistenceException, ElementNotFoundException { +        String name = rs.getString("VehicleVersion.name"); +        long vehicleId = Long.parseLong(name.split("-")[1]); +        return Vehicle.builder() +                .id(vehicleId) +                .name(rs.getString("VehicleVersion.name")) +                .constructionType( +                        ConstructionType.values()[rs.getInt("VehicleVersion.constructionType")]) +                .type(VehicleType.valueOf(rs.getString("VehicleVersion.type"))) +                .status(vehicleDAO.get(vehicleId).status()) +                .hasNef(rs.getBoolean("VehicleVersion.hasNef")) +                .registrations(registrationDAO.list(rs.getLong("VehicleVersion.id"))) +                .build(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDAO.java new file mode 100644 index 0000000..4a35f86 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDAO.java @@ -0,0 +1,28 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import java.util.Set; + +public interface RegistrationDAO { + +    /** +     * Persist the given registration. +     * +     * @param vehicleId the id of the target vehicle +     * @param registrations that should be stored +     * @return a list of the ids that were assigned +     * @throws PersistenceException if the registration could not be persisted +     */ +    Set<Long> add(long vehicleId, Set<Registration> registrations) throws PersistenceException; + +    /** +     * Make registration with the given id inactive. +     * +     * @param id of the registration that should be made inactive +     * @throws ElementNotFoundException if no registration with the given id exists +     * @throws PersistenceException if the registration could not be made inactive +     */ +    void remove(long id) throws ElementNotFoundException, PersistenceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDatabaseDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDatabaseDAO.java new file mode 100644 index 0000000..b624056 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/RegistrationDatabaseDAO.java @@ -0,0 +1,189 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee.EducationLevel; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.util.JDBCConnectionManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.time.Instant; +import java.time.LocalDate; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Repository; + +@Repository +public class RegistrationDatabaseDAO implements RegistrationDAO { + +    private JDBCConnectionManager jdbcConnectionManager; +    private EmployeeDAO employeePersistence; + +    @Autowired +    public RegistrationDatabaseDAO( +            JDBCConnectionManager jdbcConnectionManager, EmployeeDAO employeePersistence) { +        this.jdbcConnectionManager = jdbcConnectionManager; +        this.employeePersistence = employeePersistence; +    } + +    private long getVehicleVersionId(long vehicleId) throws PersistenceException { +        String sqlGetVehicleVersionId = "SELECT * FROM vehicle WHERE id = ?"; +        try (PreparedStatement stmt = +                jdbcConnectionManager.getConnection().prepareStatement(sqlGetVehicleVersionId)) { +            stmt.setLong(1, vehicleId); +            try (ResultSet rs = stmt.executeQuery()) { +                if (rs.next()) { +                    return rs.getLong("version"); +                } else { +                    throw new PersistenceException("vehicle id not found"); +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    private long getEmployeeVersionId(long employeeId) throws PersistenceException { +        String sqlGetEmployeeVersionId = "SELECT * FROM employee WHERE id = ?"; +        try (PreparedStatement stmt = +                jdbcConnectionManager.getConnection().prepareStatement(sqlGetEmployeeVersionId)) { +            stmt.setLong(1, employeeId); +            try (ResultSet rs = stmt.executeQuery()) { +                if (rs.next()) { +                    return rs.getLong("version"); +                } else { +                    throw new PersistenceException("employee id not found"); +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    @Override +    public Set<Long> add(long vehicleId, Set<Registration> registrations) +            throws PersistenceException { +        String sql = +                "INSERT INTO Registration (vehicleId, employeeId, start, end, active) VALUES (?,?,?,?,?)"; +        String sql2 = "UPDATE Vehicle SET status = 'FREI_WACHE' WHERE id = ?;"; + +        Set<Long> vehicleIds = new HashSet<>(); + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); + +            try (PreparedStatement pstmt = +                    con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { + +                // vehicleId is a Vehicle.id as it comes from GUI => fetch VehicleVersion.id +                pstmt.setLong(1, getVehicleVersionId(vehicleId)); + +                for (Registration r : registrations) { +                    pstmt.setLong(2, getEmployeeVersionId(r.employee().id())); +                    pstmt.setObject(3, OffsetDateTime.ofInstant(r.start(), ZoneId.systemDefault())); +                    pstmt.setObject(4, OffsetDateTime.ofInstant(r.end(), ZoneId.systemDefault())); +                    pstmt.setBoolean(5, true); +                    pstmt.addBatch(); +                } + +                pstmt.executeBatch(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    while (rs.next()) vehicleIds.add(rs.getLong(1)); +                } +            } + +            try (PreparedStatement pstmt = con.prepareStatement(sql2)) { +                pstmt.setLong(1, vehicleId); +                if (pstmt.executeUpdate() != 1) { +                    con.rollback(); +                    throw new PersistenceException("Failed to persist registration"); +                } +            } + +            con.commit(); +            return vehicleIds; +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void remove(long id) throws ElementNotFoundException, PersistenceException { +        String sql = "UPDATE Registration SET active = 0, end = ? WHERE id = ?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); + +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setObject(1, OffsetDateTime.ofInstant(Instant.now(), ZoneId.systemDefault())); +                pstmt.setLong(2, id); + +                if (pstmt.executeUpdate() != 1) +                    throw new ElementNotFoundException("No such registrationId exists"); +            } + +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    protected List<Registration> list(long vehicleId) throws PersistenceException { + +        String sql = +                "SELECT * FROM Registration r " +                        + "JOIN EmployeeVersion ev ON ev.id = r.employeeId " +                        + "JOIN VehicleVersion vv ON vv.id = r.vehicleId " +                        + "WHERE r.vehicleId = ?"; + +        try (PreparedStatement stmt = jdbcConnectionManager.getConnection().prepareStatement(sql)) { + +            List<Registration> registrationList = new ArrayList<>(); +            stmt.setLong(1, vehicleId); // is vehicle version id! +            ResultSet rs = stmt.executeQuery(); +            while (rs.next()) { + +                Employee employee = +                        Employee.builder() +                                .id(rs.getLong("EmployeeVersion.id")) +                                .name(rs.getString("EmployeeVersion.name")) +                                .birthday(rs.getObject("EmployeeVersion.birthday", LocalDate.class)) +                                .educationLevel( +                                        EducationLevel.valueOf( +                                                rs.getString("EmployeeVersion.educationLevel"))) +                                .isDriver(rs.getBoolean("EmployeeVersion.isDriver")) +                                .isPilot(rs.getBoolean("EmployeeVersion.isPilot")) +                                .build(); + +                Registration registration = +                        Registration.builder() +                                .id(rs.getLong("Registration.id")) +                                .start( +                                        (rs.getObject("Registration.start", OffsetDateTime.class)) +                                                .toInstant()) +                                .end( +                                        (rs.getObject("Registration.end", OffsetDateTime.class)) +                                                .toInstant()) +                                .employee(employee) +                                .build(); + +                registrationList.add(registration); +            } + +            return registrationList; +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDAO.java new file mode 100644 index 0000000..46d1853 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDAO.java @@ -0,0 +1,54 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import java.util.Set; + +public interface VehicleDAO { + +    /** +     * Persist the given vehicle. +     * +     * @param vehicle that should be stored +     * @return the id that was assigned +     * @throws PersistenceException if the vehicle could not be persisted +     */ +    long add(Vehicle vehicle) throws PersistenceException; + +    /** +     * Update the given vehicle. +     * +     * @param vehicle that should be updated +     * @throws ElementNotFoundException if no vehicle with the given id exists +     * @throws PersistenceException if the vehicle could not be updated +     */ +    void update(Vehicle vehicle) throws ElementNotFoundException, PersistenceException; + +    /** +     * Get all stored vehicles. +     * +     * @return list containing all stored vehicles +     * @throws PersistenceException if loading the stored vehicles failed +     */ +    Set<Vehicle> list() throws PersistenceException; + +    /** +     * Returns the vehicle with the given id. +     * +     * @param vehicleId id of the vehicle that should be returned +     * @return vehicle with the given id +     * @throws ElementNotFoundException if no vehicle with the given id exists +     * @throws PersistenceException if the vehicle could not be loaded +     */ +    Vehicle get(long vehicleId) throws ElementNotFoundException, PersistenceException; + +    /** +     * Remove vehicle with the given id from the store. +     * +     * @param id of the vehicle that should be removed +     * @throws ElementNotFoundException if no vehicle with the given id exists +     * @throws PersistenceException if the vehicle could not be removed +     */ +    void remove(long id) throws ElementNotFoundException, PersistenceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDatabaseDAO.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDatabaseDAO.java new file mode 100644 index 0000000..8cef65e --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dao/VehicleDatabaseDAO.java @@ -0,0 +1,211 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.ElementNotFoundException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.PersistenceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.ConstructionType; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.VehicleType; +import at.ac.tuwien.sepm.assignment.groupphase.util.JDBCConnectionManager; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashSet; +import java.util.Set; +import org.springframework.stereotype.Repository; + +@Repository +public class VehicleDatabaseDAO implements VehicleDAO { + +    private final JDBCConnectionManager jdbcConnectionManager; +    private RegistrationDatabaseDAO registrationDatabaseDao; + +    public VehicleDatabaseDAO( +            JDBCConnectionManager j, RegistrationDatabaseDAO registrationDatabaseDao) { +        jdbcConnectionManager = j; +        this.registrationDatabaseDao = registrationDatabaseDao; +    } + +    @Override +    public long add(Vehicle v) throws PersistenceException { +        String sql = +                "INSERT INTO VehicleVersion (name,hasNef,constructionType,type) VALUES (?,?,?,?)"; +        String sql2 = "INSERT INTO Vehicle (version,status) VALUES (?,?)"; +        String sql3 = "UPDATE VehicleVersion SET name=? WHERE id=?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); +            String name = ""; +            long version, id; + +            try (PreparedStatement pstmt = +                    con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { +                pstmt.setString(1, name); +                pstmt.setBoolean(2, v.hasNef()); +                pstmt.setInt(3, v.constructionType().ordinal()); +                pstmt.setString(4, v.type().name()); +                pstmt.executeUpdate(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    if (!rs.next()) +                        throw new PersistenceException("Failed to insert into VehicleVersion"); + +                    version = rs.getLong(1); +                } +            } + +            try (PreparedStatement pstmt = +                    con.prepareStatement(sql2, Statement.RETURN_GENERATED_KEYS)) { +                pstmt.setLong(1, version); +                pstmt.setInt(2, Status.ABGEMELDET.ordinal()); +                pstmt.executeUpdate(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    if (!rs.next()) { +                        con.rollback(); +                        throw new PersistenceException("Failed to insert into Vehicle"); +                    } + +                    id = rs.getLong(1); +                } + +                name = v.type().name() + "-" + id; +            } + +            try (PreparedStatement pstmt = con.prepareStatement(sql3)) { +                pstmt.setString(1, name); +                pstmt.setLong(2, version); + +                if (pstmt.executeUpdate() != 1) { +                    con.rollback(); +                    throw new PersistenceException("Failed to update VehicleVersion"); +                } +            } + +            con.commit(); +            return id; +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void update(Vehicle v) throws ElementNotFoundException, PersistenceException { +        String sql = "SELECT version FROM Vehicle WHERE id = ?"; +        String sql2 = +                "MERGE INTO VehicleVersion(name, constructionType, type, hasNef)" +                        + " KEY(name, constructionType, type, hasNef) VALUES(?, ?, ?, ?)"; +        String sql3 = "UPDATE Vehicle SET version = ?, status = ? WHERE id = ?"; + +        long versionId; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            con.setAutoCommit(false); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setLong(1, v.id()); + +                try (ResultSet rs = pstmt.executeQuery()) { +                    if (!rs.next()) throw new ElementNotFoundException("No such vehicleId exists"); + +                    versionId = rs.getLong(1); +                } +            } + +            try (PreparedStatement pstmt = +                    con.prepareStatement(sql2, Statement.RETURN_GENERATED_KEYS)) { +                pstmt.setString(1, v.type().name() + "-" + v.id()); +                pstmt.setString(2, v.constructionType().name()); +                pstmt.setString(3, v.type().name()); +                pstmt.setBoolean(4, v.hasNef()); +                pstmt.executeUpdate(); + +                try (ResultSet rs = pstmt.getGeneratedKeys()) { +                    if (rs.next()) { +                        // version changed, update it +                        versionId = rs.getLong(1); +                    } +                } +            } + +            try (PreparedStatement pstmt = con.prepareStatement(sql3)) { +                pstmt.setLong(1, versionId); +                pstmt.setString(2, v.status().name()); +                pstmt.setLong(3, v.id()); +                pstmt.executeUpdate(); +            } + +            con.commit(); +        } catch (SQLException e) { +            jdbcConnectionManager.rollbackConnection(); +            throw new PersistenceException(e); +        } +    } + +    @Override +    public Set<Vehicle> list() throws PersistenceException { +        Set<Vehicle> result = new HashSet<>(); + +        String sql = +                "Select * from VehicleVersion, Vehicle where VehicleVersion.id=Vehicle.version"; + +        try (PreparedStatement pstmt = +                jdbcConnectionManager.getConnection().prepareStatement(sql)) { +            pstmt.executeQuery(); +            try (ResultSet rs = pstmt.getResultSet()) { +                while (rs.next()) { +                    result.add(vehicleFromRS(rs)); +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +        return result; +    } + +    @Override +    public Vehicle get(long id) throws ElementNotFoundException, PersistenceException { +        String sql = +                "SELECT *" +                        + " FROM Vehicle a" +                        + " INNER JOIN VehicleVersion b" +                        + " ON version = b.id" +                        + " WHERE a.id = ?"; + +        try { +            Connection con = jdbcConnectionManager.getConnection(); +            try (PreparedStatement pstmt = con.prepareStatement(sql)) { +                pstmt.setLong(1, id); + +                try (ResultSet rs = pstmt.executeQuery()) { +                    if (!rs.first()) throw new ElementNotFoundException("No such vehicle exists"); + +                    return vehicleFromRS(rs); +                } +            } +        } catch (SQLException e) { +            throw new PersistenceException(e); +        } +    } + +    @Override +    public void remove(long id) throws ElementNotFoundException, PersistenceException { +        throw new UnsupportedOperationException(); +    } + +    private Vehicle vehicleFromRS(ResultSet rs) throws SQLException, PersistenceException { +        return Vehicle.builder() +                .id(rs.getLong("Vehicle.id")) +                .name(rs.getString("name")) +                .constructionType(ConstructionType.values()[rs.getInt("constructionType")]) +                .type(VehicleType.valueOf(rs.getString("type"))) +                .status(Status.values()[rs.getInt("status")]) +                .hasNef(rs.getBoolean("hasNef")) +                .registrations(registrationDatabaseDao.list(rs.getLong("Vehicle.version"))) +                .build(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Employee.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Employee.java new file mode 100644 index 0000000..f45550e --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Employee.java @@ -0,0 +1,51 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import com.google.auto.value.AutoValue; +import java.time.LocalDate; + +@AutoValue +public abstract class Employee { +    public enum EducationLevel { +        RS, +        NFS, +        NKV, +        NKA, +        NKI, +        NA +    } + +    public abstract long id(); + +    public abstract String name(); + +    public abstract LocalDate birthday(); + +    public abstract EducationLevel educationLevel(); + +    public abstract boolean isDriver(); + +    public abstract boolean isPilot(); + +    public static Builder builder() { +        return new AutoValue_Employee.Builder().id(0); +    } + +    @AutoValue.Builder +    public abstract static class Builder { +        public abstract Builder id(long id); + +        public abstract Builder name(String name); + +        public abstract Builder birthday(LocalDate birthday); + +        public abstract Builder educationLevel(EducationLevel educationLevel); + +        public abstract Builder isDriver(boolean isDriver); + +        public abstract Builder isPilot(boolean isPilot); + +        public abstract Employee build(); +    } + +    public abstract Builder toBuilder(); +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/EmployeeValidator.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/EmployeeValidator.java new file mode 100644 index 0000000..b03fa04 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/EmployeeValidator.java @@ -0,0 +1,23 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidEmployeeException; + +public class EmployeeValidator { + +    public static boolean validate(Employee employee) throws InvalidEmployeeException { + +        if (employee.name() == null || employee.name().trim().length() == 0) { +            throw new InvalidEmployeeException("Name darf nicht leer sein!"); +        } + +        if (employee.birthday() == null) { +            throw new InvalidEmployeeException("Geburtsdatum darf nicht leer sein!"); +        } + +        if (employee.educationLevel() == null) { +            throw new InvalidEmployeeException("Ausbildungsgrad darf nicht leer sein!"); +        } + +        return true; +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Operation.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Operation.java new file mode 100644 index 0000000..e119622 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Operation.java @@ -0,0 +1,70 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import com.google.auto.value.AutoValue; +import java.time.Instant; +import java.util.Set; +import javax.annotation.Nullable; + +@AutoValue +public abstract class Operation { +    public enum Severity { +        A, +        B, +        C, +        D, +        E, +        O, +    } + +    public enum Status { +        ACTIVE, +        COMPLETED, +        CANCELLED, +    } + +    public abstract long id(); + +    public abstract String opCode(); + +    @Nullable +    public abstract Severity severity(); + +    public abstract Status status(); + +    public abstract Set<Vehicle> vehicles(); + +    @Nullable +    public abstract Instant created(); + +    public abstract String destination(); + +    @Nullable +    public abstract String additionalInfo(); + +    public static Builder builder() { +        return new AutoValue_Operation.Builder().id(0); +    } + +    @AutoValue.Builder +    public abstract static class Builder { +        public abstract Builder id(long id); + +        public abstract Builder opCode(String opCode); + +        public abstract Builder severity(Severity severity); + +        public abstract Builder status(Status status); + +        public abstract Builder vehicles(Set<Vehicle> vehicles); + +        public abstract Builder created(Instant created); + +        public abstract Builder destination(String destination); + +        public abstract Builder additionalInfo(String additionalInfo); + +        public abstract Operation build(); +    } + +    public abstract Builder toBuilder(); +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Registration.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Registration.java new file mode 100644 index 0000000..a12c038 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Registration.java @@ -0,0 +1,34 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import com.google.auto.value.AutoValue; +import java.time.Instant; + +@AutoValue +public abstract class Registration { +    public abstract long id(); + +    public abstract Instant start(); + +    public abstract Instant end(); + +    public abstract Employee employee(); + +    public static Builder builder() { +        return new AutoValue_Registration.Builder().id(0); +    } + +    @AutoValue.Builder +    public abstract static class Builder { +        public abstract Builder id(long id); + +        public abstract Builder start(Instant start); + +        public abstract Builder end(Instant end); + +        public abstract Builder employee(Employee employee); + +        public abstract Registration build(); +    } + +    public abstract Builder toBuilder(); +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/RegistrationValidator.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/RegistrationValidator.java new file mode 100644 index 0000000..a2cb8c1 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/RegistrationValidator.java @@ -0,0 +1,174 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidRegistrationException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee.EducationLevel; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.VehicleType; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +public class RegistrationValidator { + +    private RegistrationValidator() {} + +    public static void validate(Vehicle vehicle, Set<Registration> registrations) +            throws InvalidVehicleException, InvalidRegistrationException { +        /* +        Vehicles and Employees are assumed to be valid. +        They have been checked at creation, and for them to be checked again, access to +        VehicleValidator and EmployeeValidator are needed, which are not available at this time. +         */ +        /* +        The method used here goes as follows: All given employees are inspected in regards to their +        qualifications, and added to the appropriate lists of the roles they could fill. +        For example, an NFS, who is also a driver, would be added to the lists driverIds, nfsIds +        and rsIds (because an NFS can always substitute an RS). +        Afterwards, the number of people is checked according to the chosen vehicle type, and if +        the number is okay, the program tries to find a valid combination of roles for the vehicle. +        For example, for an RTW, first a driver is chosen, their ID marked as found in the aptly +        titled HashMap, and then for the second RS, the list of RS is checked, excluding the chosen +        driver. If no other valid RS is found, the next possible driver is chosen, and so on. If no +        valid combination is found, an InvalidRegistrationException is thrown. +         */ +        List<Long> pilotIds = new LinkedList<>(); +        List<Long> driverIds = new LinkedList<>(); +        List<Long> naIds = new LinkedList<>(); +        List<Long> nfsIds = new LinkedList<>(); +        List<Long> rsIds = new LinkedList<>(); +        HashMap<Long, Boolean> found = +                new HashMap<>(); // needed later in DFS, checks that no person is chosen twice +        int total = 0; +        for (Registration registration : registrations) { +            total++; +            if (found.put(registration.employee().id(), false) != null) { +                throw new InvalidRegistrationException( +                        "Person mit der ID: " +                                + registration.employee().id() +                                + " wurde mehrmals hinzugefügt!"); +            } +            if (registration.employee().isPilot()) { +                pilotIds.add(registration.employee().id()); +            } +            if (registration.employee().isDriver()) { +                driverIds.add(registration.employee().id()); +            } +            if (registration.employee().educationLevel() == EducationLevel.NA) { +                naIds.add(registration.employee().id()); +                nfsIds.add(registration.employee().id()); +                rsIds.add(registration.employee().id()); +            } else if (isNFS(registration.employee())) { +                nfsIds.add(registration.employee().id()); +                rsIds.add(registration.employee().id()); +            } else { // only RS left +                rsIds.add(registration.employee().id()); +            } +        } +        if (total <= 0) { +            throw new InvalidRegistrationException("Kein Personal ausgewählt!"); +        } +        if (vehicle.type() == VehicleType.NAH) { +            /* +            NAH +            1 Pilot +            1 NFS +            1 NA +            3-4 Personen +             */ +            if (total < 3) { +                throw new InvalidRegistrationException("Zu wenig Personal für NAH!"); +            } else if (total > 4) { +                throw new InvalidRegistrationException("Zu viel Personal für NAH!"); +            } +            for (long pilot_id : pilotIds) { +                found.put(pilot_id, true); +                for (long na_id : naIds) { +                    if (found.get(na_id)) continue; +                    found.put(na_id, true); +                    for (long nfs_id : nfsIds) { +                        if (found.get(nfs_id)) continue; +                        return; +                    } +                    found.put(na_id, false); +                } +                found.put(pilot_id, false); +            } +            throw new InvalidRegistrationException( +                    "Keine gültige Kombination von Personen für NAH!"); +        } else if (vehicle.type() == VehicleType.NEF) { +            /* +            NEF +            1 Driver (has to be NFS) +            1 NA +             */ +            if (total < 2) { +                throw new InvalidRegistrationException("Zu wenig Personal für NEF!"); +            } else if (total > 3) { +                throw new InvalidRegistrationException("Zu viel Personal für NEF!"); +            } +            for (long driver_id : driverIds) { +                if (!nfsIds.contains(driver_id)) +                    continue; // if possible driver is not NFS, skip him +                found.put(driver_id, true); +                for (long na_id : naIds) { +                    if (found.get(na_id)) continue; +                    return; +                } +                found.put(driver_id, false); +            } +            throw new InvalidRegistrationException( +                    "Keine gültige Kombination von Personen für NEF!"); +        } else if (vehicle.type() == VehicleType.BKTW) { +            /* +            BKTW +            1 Driver +             */ +            if (total > 3) { +                throw new InvalidRegistrationException("Zu viel Personal für BKTW!"); +            } +            if (!driverIds.isEmpty()) { +                return; +            } +            throw new InvalidRegistrationException("Kein Fahrer gefunden für BKTW!"); +        } else { // KTW or RTW, both have the same requirements +            /* +            RTW/KTW +            1 Driver +            1 RS +             */ +            if (total < 2) { +                throw new InvalidRegistrationException( +                        "Zu wenig Personal für " + vehicle.type().name() + "!"); +            } else if (total > 4) { +                throw new InvalidRegistrationException( +                        "Zu viel Persoanl für " + vehicle.type().name() + "!"); +            } +            for (long driver_id : driverIds) { // driver includes rs +                found.put(driver_id, true); +                for (long rs_id : rsIds) { +                    if (found.get(rs_id)) continue; +                    return; +                } +            } +            throw new InvalidRegistrationException( +                    "Keine gültige Kombination von Personen für " + vehicle.type().name() + "!"); +        } +    } + +    private static boolean isNFS(Employee employee) { +        EducationLevel educationLevel = employee.educationLevel(); +        switch (educationLevel) { +            case NFS: +                return true; +            case NKA: +                return true; +            case NKI: +                return true; +            case NKV: +                return true; +            default: +                return false; +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Vehicle.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Vehicle.java new file mode 100644 index 0000000..c2033f5 --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/dto/Vehicle.java @@ -0,0 +1,73 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto; + +import com.google.auto.value.AutoValue; +import java.util.List; +import javax.annotation.Nullable; + +@AutoValue +public abstract class Vehicle { +    public enum ConstructionType { +        NORMAL, +        HOCHDACH, +        MITTELHOCHDACH, +    } + +    public enum VehicleType { +        BKTW, +        KTW_B, +        KTW, +        RTW, +        NEF, +        NAH, +    } + +    public enum Status { +        ABGEMELDET, +        FREI_WACHE, +        FREI_FUNK, +        ZUM_BERUFUNGSORT, +        AM_BERUFUNGSORT, +        ZUM_ZIELORT, +        AM_ZIELORT, +    } + +    public abstract long id(); + +    public abstract String name(); + +    public abstract ConstructionType constructionType(); + +    public abstract VehicleType type(); + +    public abstract Status status(); + +    public abstract boolean hasNef(); + +    @Nullable +    public abstract List<Registration> registrations(); + +    public static Builder builder() { +        return new AutoValue_Vehicle.Builder().id(0); +    } + +    @AutoValue.Builder +    public abstract static class Builder { +        public abstract Builder id(long id); + +        public abstract Builder name(String name); + +        public abstract Builder constructionType(ConstructionType constructionType); + +        public abstract Builder type(VehicleType type); + +        public abstract Builder status(Status status); + +        public abstract Builder hasNef(boolean hasNef); + +        public abstract Builder registrations(List<Registration> registrations); + +        public abstract Vehicle build(); +    } + +    public abstract Builder toBuilder(); +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeService.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeService.java new file mode 100644 index 0000000..5beabaa --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeService.java @@ -0,0 +1,46 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidEmployeeException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import java.util.Set; + +public interface EmployeeService { + +    /** +     * Add given employee to the store. +     * +     * @param employee that should be added to the store +     * @return the id that was assigned +     * @throws InvalidEmployeeException if the employee is invalid +     * @throws ServiceException if the employee could not be persisted +     */ +    long add(Employee employee) throws InvalidEmployeeException, ServiceException; + +    /** +     * Update the given employee. +     * +     * @param employee that should be updated +     * @return the updated employee +     * @throws InvalidEmployeeException if the employee is invalid +     * @throws ServiceException if the updated employee could not be persisted +     */ +    Employee update(Employee employee) throws InvalidEmployeeException, ServiceException; + +    /** +     * Get all stored employees. +     * +     * @return list containing all stored employees +     * @throws ServiceException if loading the stored employees failed +     */ +    Set<Employee> list() throws ServiceException; + +    /** +     * Remove employee with the given id from the store. +     * +     * @param id of the employee that should be removed +     * @throws InvalidEmployeeException if given employee id is invalid or does not exist +     * @throws ServiceException if the employee could not be removed from the store +     */ +    void remove(long id) throws InvalidEmployeeException, ServiceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeServiceImpl.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeServiceImpl.java new file mode 100644 index 0000000..a08b03e --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/EmployeeServiceImpl.java @@ -0,0 +1,59 @@ +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.InvalidEmployeeException; +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.EmployeeDAO; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Employee; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.EmployeeValidator; +import java.util.Set; +import org.springframework.stereotype.Service; + +@Service +public class EmployeeServiceImpl implements EmployeeService { + +    private final EmployeeDAO employeePersistence; + +    public EmployeeServiceImpl(EmployeeDAO employeePersistence) { +        this.employeePersistence = employeePersistence; +    } + +    @Override +    public long add(Employee employee) throws InvalidEmployeeException, ServiceException { + +        EmployeeValidator.validate(employee); +        try { +            return employeePersistence.add(employee); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } +    } + +    @Override +    public Employee update(Employee employee) throws InvalidEmployeeException, ServiceException { + +        EmployeeValidator.validate(employee); +        try { +            employeePersistence.update(employee); +            return employee; +        } catch (ElementNotFoundException | PersistenceException e) { +            throw new ServiceException(e); +        } +    } + +    @Override +    public Set<Employee> list() throws ServiceException { + +        try { +            return employeePersistence.list(); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } +    } + +    @Override +    public void remove(long id) throws InvalidEmployeeException, ServiceException { +        throw new UnsupportedOperationException(); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationService.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationService.java new file mode 100644 index 0000000..42b23bb --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationService.java @@ -0,0 +1,70 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service; + +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.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Operation.Status; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import java.util.EnumSet; +import java.util.Set; +import java.util.SortedSet; + +public interface OperationService { + +    /** +     * Add given operation to the store. +     * +     * @param operation that should be added to the store +     * @return the id that was assigned +     * @throws InvalidOperationException if the operation is invalid +     * @throws ServiceException if the operation could not be persisted +     */ +    long add(Operation operation) throws InvalidOperationException, ServiceException; + +    /** +     * Request new vehicles to the given operation. +     * +     * @param operationId id of the operation that the vehicles should be send to +     * @param vehicleIds the ids of the vehicles that should be send to the given operation +     * @throws InvalidOperationException if the operationId is invalid or does not exist +     * @throws InvalidVehicleException if one of the vehicle ids is invalid or does not exist +     * @throws ServiceException if the vehicles could not be loaded or the operation could not be +     *     persisted +     */ +    void requestVehicles(long operationId, Set<Long> vehicleIds) +            throws InvalidOperationException, InvalidVehicleException, ServiceException; + +    /** +     * Completes the given operation with the specified status. +     * +     * @param operationId id of the operation that should be completed +     * @param status of the completed operation, either {@link Status#COMPLETED} or {@link +     *     Status#CANCELLED} +     * @throws InvalidOperationException if the operationId is invalid or does not exist +     * @throws ServiceException if the operation could not be persisted +     */ +    void complete(long operationId, Status status) +            throws InvalidOperationException, ServiceException; + +    /** +     * Get all available vehicles, sorted by how well they fit to the given opCode, starting with +     * the best fitting. +     * +     * @param opCode the operation code that is used to determine the ranking +     * @return a sorted list containing all available vehicles +     * @throws InvalidOperationException if the opCode is invalid +     * @throws ServiceException if loading the stored vehicles failed +     */ +    SortedSet<Vehicle> rankVehicles(String opCode) +            throws InvalidOperationException, ServiceException; + +    /** +     * Get all stored operations with matching status. +     * +     * @param statuses set containing all statuses that should be matched +     * @return list containing all matched operations +     * @throws ServiceException if loading the stored operations failed +     */ +    Set<Operation> list(EnumSet<Status> statuses) throws ServiceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationServiceImpl.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationServiceImpl.java new file mode 100644 index 0000000..0c350fe --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/OperationServiceImpl.java @@ -0,0 +1,286 @@ +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<Long>) 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<Long> vehicleIds) +            throws InvalidOperationException, InvalidVehicleException, ServiceException { +        Set<Vehicle> 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<Vehicle> rankVehicles(String opCode) +            throws InvalidOperationException, ServiceException { +        Set<Vehicle> vehicles = +                vehicleService.list(EnumSet.complementOf(EnumSet.of(Vehicle.Status.ABGEMELDET))); + +        List<Predicate<Vehicle>> priorities = new ArrayList<>(); +        Predicate<Vehicle> ktw = v -> v.type() == VehicleType.KTW; +        Predicate<Vehicle> rtwNoNEF = v -> v.type() == VehicleType.RTW && !v.hasNef(); +        Predicate<Vehicle> rtwNEF = v -> v.type() == VehicleType.RTW && v.hasNef(); +        Predicate<Vehicle> nef = v -> v.type() == VehicleType.NEF; +        Predicate<Vehicle> 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<Vehicle> vehicleComparator = +                (v1, v2) -> { +                    for (Predicate<Vehicle> priority : priorities) { +                        if (priority.test(v1)) { +                            return -1; +                        } +                        if (priority.test(v2)) { +                            return +1; +                        } +                    } +                    return 0; +                }; + +        Supplier<TreeSet<Vehicle>> supplier = () -> new TreeSet<>(vehicleComparator); + +        return vehicles.stream().collect(Collectors.toCollection(supplier)); +    } + +    @Override +    public Set<Operation> list(EnumSet<Status> statuses) throws ServiceException { +        try { +            Set<Operation> 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)); +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationService.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationService.java new file mode 100644 index 0000000..91577dc --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationService.java @@ -0,0 +1,32 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidRegistrationException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import java.util.Set; + +public interface RegistrationService { + +    /** +     * Register employee to a vehicle. +     * +     * @param vehicleId the id of the target vehicle +     * @param registrations that should be added to the vehicle +     * @return the list of ids that were assigned +     * @throws InvalidVehicleException if the vehicleId is invalid or does not exist +     * @throws InvalidRegistrationException if the registration is invalid +     * @throws ServiceException if the registration could not be persisted +     */ +    Set<Long> add(long vehicleId, Set<Registration> registrations) +            throws InvalidVehicleException, InvalidRegistrationException, ServiceException; + +    /** +     * Remove given registration from the store. +     * +     * @param registrationId the id of the registration that should be removed +     * @throws InvalidRegistrationException if the registration is invalid or does not exist +     * @throws ServiceException if the registration could not be removed from the store +     */ +    void remove(long registrationId) throws InvalidRegistrationException, ServiceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationServiceImpl.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationServiceImpl.java new file mode 100644 index 0000000..a6a1dfe --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/RegistrationServiceImpl.java @@ -0,0 +1,60 @@ +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.InvalidRegistrationException; +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.RegistrationDAO; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dao.VehicleDAO; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Registration; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.RegistrationValidator; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import java.util.Set; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +@Service +public class RegistrationServiceImpl implements RegistrationService { + +    private final RegistrationDAO registrationDAO; +    private final VehicleDAO vehicleDAO; + +    @Autowired +    public RegistrationServiceImpl(RegistrationDAO registrationDAO, VehicleDAO vehicleDAO) { +        this.registrationDAO = registrationDAO; +        this.vehicleDAO = vehicleDAO; +    } + +    @Override +    public Set<Long> add(long vehicleId, Set<Registration> registrations) +            throws InvalidVehicleException, InvalidRegistrationException, ServiceException { + +        if (vehicleId <= 0) throw new InvalidVehicleException("VehicleId invalid"); + +        try { +            Vehicle vehicle = vehicleDAO.get(vehicleId); + +            RegistrationValidator.validate(vehicle, registrations); + +            return registrationDAO.add(vehicle.id(), registrations); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } catch (ElementNotFoundException e) { +            throw new InvalidVehicleException(e); +        } +    } + +    @Override +    public void remove(long registrationId) throws InvalidRegistrationException, ServiceException { +        if (registrationId <= 0) throw new InvalidRegistrationException("RegistrationId invalid"); + +        try { +            registrationDAO.remove(registrationId); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } catch (ElementNotFoundException e) { +            throw new InvalidRegistrationException(e); +        } +    } +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleService.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleService.java new file mode 100644 index 0000000..f8e303d --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleService.java @@ -0,0 +1,49 @@ +package at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.service; + +import at.ac.tuwien.sepm.assignment.groupphase.exception.InvalidVehicleException; +import at.ac.tuwien.sepm.assignment.groupphase.exception.ServiceException; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import java.util.EnumSet; +import java.util.Set; + +public interface VehicleService { + +    /** +     * Add given vehicle to the store. +     * +     * @param vehicle that should be added to the store +     * @return the id that was assigned +     * @throws InvalidVehicleException if the vehicle is invalid +     * @throws ServiceException if the vehicle could not be persisted +     */ +    long add(Vehicle vehicle) throws InvalidVehicleException, ServiceException; + +    /** +     * Update the given vehicle. +     * +     * @param vehicle that should be updated +     * @return the updated vehicle +     * @throws InvalidVehicleException if the vehicle is invalid +     * @throws ServiceException if the updated vehicle could not be persisted +     */ +    Vehicle update(Vehicle vehicle) throws InvalidVehicleException, ServiceException; + +    /** +     * Get all stored vehicles with matching status. +     * +     * @param statuses set containing all statuses that should be matched +     * @return list containing all stored vehicles +     * @throws ServiceException if loading the stored vehicles failed +     */ +    Set<Vehicle> list(EnumSet<Status> statuses) throws ServiceException; + +    /** +     * Remove vehicle with the given id from the store. +     * +     * @param id of the vehicle that should be removed +     * @throws InvalidVehicleException if given vehicle id is invalid or does not exist +     * @throws ServiceException if the vehicle could not be removed from the store +     */ +    void remove(long id) throws InvalidVehicleException, ServiceException; +} diff --git a/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleServiceImpl.java b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleServiceImpl.java new file mode 100644 index 0000000..a68720d --- /dev/null +++ b/src/main/java/at/ac/tuwien/sepm/assignment/groupphase/missioncontrol/service/VehicleServiceImpl.java @@ -0,0 +1,116 @@ +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.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.VehicleDAO; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.ConstructionType; +import at.ac.tuwien.sepm.assignment.groupphase.missioncontrol.dto.Vehicle.Status; +import java.util.EnumSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; + +@Service +public class VehicleServiceImpl implements VehicleService { + +    private VehicleDAO vehiclePersistence; + +    public VehicleServiceImpl(VehicleDAO vehiclePersistence) { +        this.vehiclePersistence = vehiclePersistence; +    } + +    public long add(Vehicle vehicle) throws InvalidVehicleException, ServiceException { +        if (!CollectionUtils.isEmpty(vehicle.registrations())) { +            throw new InvalidVehicleException( +                    "Fahrzeug kann nicht mit Anmeldungen erstellt werden"); +        } + +        validateVehicle(vehicle); +        try { +            vehiclePersistence.add(vehicle); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } +        return 0; +    } + +    public Vehicle update(Vehicle vehicle) throws InvalidVehicleException, ServiceException { +        validateVehicle(vehicle); +        try { +            vehiclePersistence.update(vehicle); +        } catch (ElementNotFoundException e) { +            throw new ServiceException("Element not found", e); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } +        return vehicle; +    } + +    protected static void validateVehicle(Vehicle vehicle) throws InvalidVehicleException { +        switch (vehicle.type()) { +            case RTW: +                if (vehicle.constructionType() == ConstructionType.NORMAL) { +                    throw new InvalidVehicleException("RTW darf kein Normales Dach haben"); +                } else if (vehicle.constructionType() == ConstructionType.MITTELHOCHDACH) { +                    throw new InvalidVehicleException("RTW darf kein Mittelhochdach haben"); +                } +                break; +            case KTW: +                if (vehicle.constructionType() == ConstructionType.NORMAL) { +                    throw new InvalidVehicleException("KTW darf kein Normales Dach haben"); +                } +                break; +            case KTW_B: +                if (vehicle.constructionType() == ConstructionType.NORMAL) { +                    throw new InvalidVehicleException("KTW-B darf kein Normales Dach haben"); +                } +                break; +            case NEF: +                if (vehicle.constructionType() == ConstructionType.MITTELHOCHDACH) { +                    throw new InvalidVehicleException("NEF darf kein Mittelhochdach haben"); +                } else if (vehicle.constructionType() == ConstructionType.HOCHDACH) { +                    throw new InvalidVehicleException("NEF darf kein Hochdach haben"); +                } +                break; +            case NAH: +                if (vehicle.constructionType() == ConstructionType.MITTELHOCHDACH) { +                    throw new InvalidVehicleException("NEF darf kein Mittelhochdach haben"); +                } else if (vehicle.constructionType() == ConstructionType.HOCHDACH) { +                    throw new InvalidVehicleException("NEF darf kein Hochdach haben"); +                } +                break; +            case BKTW: +                break; +            default: +                throw new IllegalStateException("BUG: invalid vehicle type" + vehicle.type()); +        } +    } + +    @Override +    public Set<Vehicle> list(EnumSet<Status> statuses) throws ServiceException { +        if (statuses == null) { +            throw new ServiceException("Statuses may not be null"); +        } + +        Set<Vehicle> vehicles; + +        try { +            vehicles = vehiclePersistence.list(); +        } catch (PersistenceException e) { +            throw new ServiceException(e); +        } + +        return vehicles.stream() +                .filter(vehicle -> statuses.contains(vehicle.status())) +                .collect(Collectors.toSet()); +    } + +    @Override +    public void remove(long id) throws InvalidVehicleException, ServiceException { +        throw new UnsupportedOperationException(); +    } +}  | 
