diff --git a/.idea/sqlDataSources.xml b/.idea/sqlDataSources.xml index f73128a..ef907f0 100644 --- a/.idea/sqlDataSources.xml +++ b/.idea/sqlDataSources.xml @@ -27,9 +27,12 @@ - diff --git a/db/nursingHome.db b/db/nursingHome.db index 0cdc030..b89b146 100644 Binary files a/db/nursingHome.db and b/db/nursingHome.db differ diff --git a/src/main/java/de/hitec/nhplus/Main.java b/src/main/java/de/hitec/nhplus/Main.java index 42d99f8..0a72380 100644 --- a/src/main/java/de/hitec/nhplus/Main.java +++ b/src/main/java/de/hitec/nhplus/Main.java @@ -1,11 +1,15 @@ package de.hitec.nhplus; import de.hitec.nhplus.datastorage.ConnectionBuilder; +import de.hitec.nhplus.login.LoginController; +import de.hitec.nhplus.login.User; +import de.hitec.nhplus.main.MainWindowController; import javafx.application.Application; import javafx.application.Platform; import javafx.fxml.FXMLLoader; import javafx.scene.Scene; import javafx.scene.control.TabPane; +import javafx.scene.layout.BorderPane; import javafx.stage.Stage; import java.io.IOException; @@ -33,13 +37,43 @@ public class Main extends Application { @Override public void start(Stage primaryStage) { this.primaryStage = primaryStage; - executeMainApplication(); + User user = executeLogin(); + if(user != null){ + executeMainApplication(user); + } + } + + /** + * Executes the login. + * @return User The {@link User} object for the logged-in {@link User}. + * Is {@code null}, if the login was not successful, + */ + private User executeLogin() { + try { + + FXMLLoader loader = new FXMLLoader(Main.class.getResource("/de/hitec/nhplus/login/LoginView.fxml")); + BorderPane pane = loader.load(); + Scene scene = new Scene(pane); + Stage loginStage = new Stage(); + loginStage.setTitle("NHPlus"); + loginStage.setScene(scene); + loginStage.setResizable(false); + + LoginController controller = loader.getController(); + controller.initialize(loginStage); + + loginStage.showAndWait(); + return controller.user; + } catch (IOException exception) { + exception.printStackTrace(); + return null; + } } /** * Executes the main application. */ - private void executeMainApplication() { + private void executeMainApplication(User user) { try { FXMLLoader loader = new FXMLLoader(Main.class.getResource("/de/hitec/nhplus/main/MainWindowView.fxml")); TabPane pane = loader.load(); @@ -48,6 +82,10 @@ public class Main extends Application { this.primaryStage.setTitle("NHPlus"); this.primaryStage.setScene(scene); this.primaryStage.setResizable(true); + + MainWindowController controller = loader.getController(); + controller.initialize(user); + this.primaryStage.show(); this.primaryStage.setOnCloseRequest(event -> { diff --git a/src/main/java/de/hitec/nhplus/datastorage/DaoFactory.java b/src/main/java/de/hitec/nhplus/datastorage/DaoFactory.java index 955c88d..067101a 100644 --- a/src/main/java/de/hitec/nhplus/datastorage/DaoFactory.java +++ b/src/main/java/de/hitec/nhplus/datastorage/DaoFactory.java @@ -1,5 +1,6 @@ package de.hitec.nhplus.datastorage; +import de.hitec.nhplus.login.database.UserDao; import de.hitec.nhplus.medication.database.MedicationDao; import de.hitec.nhplus.nurse.database.NurseDao; import de.hitec.nhplus.patient.database.PatientDao; @@ -63,4 +64,12 @@ public class DaoFactory { public MedicationDao createMedicationDAO() { return new MedicationDao(ConnectionBuilder.getConnection()); } + + /** + * @return A new {@link UserDao} instance with a database connection. + * @see de.hitec.nhplus.login.User User + */ + public UserDao createUserDAO() { + return new UserDao(ConnectionBuilder.getConnection()); + } } diff --git a/src/main/java/de/hitec/nhplus/fixtures/Fixtures.java b/src/main/java/de/hitec/nhplus/fixtures/Fixtures.java index 51719a3..5897378 100644 --- a/src/main/java/de/hitec/nhplus/fixtures/Fixtures.java +++ b/src/main/java/de/hitec/nhplus/fixtures/Fixtures.java @@ -46,6 +46,11 @@ public class Fixtures { medicationFixture.setupTable(connection); medicationFixture.load(); + UserFixture userFixture = new UserFixture(nursesByName); + userFixture.dropTable(connection); + userFixture.setupTable(connection); + userFixture.load(); + } catch (Exception exception) { System.out.println(exception.getMessage()); } diff --git a/src/main/java/de/hitec/nhplus/fixtures/NurseFixture.java b/src/main/java/de/hitec/nhplus/fixtures/NurseFixture.java index c5d0c0f..3da00ed 100644 --- a/src/main/java/de/hitec/nhplus/fixtures/NurseFixture.java +++ b/src/main/java/de/hitec/nhplus/fixtures/NurseFixture.java @@ -54,6 +54,12 @@ public class NurseFixture implements Fixture { true )); + nurses.add(new Nurse( + "Maria", + "Höller", + "666" + )); + NurseDao dao = DaoFactory.getInstance().createNurseDAO(); for (Nurse nurse : nurses) { dao.create(nurse); diff --git a/src/main/java/de/hitec/nhplus/fixtures/UserFixture.java b/src/main/java/de/hitec/nhplus/fixtures/UserFixture.java new file mode 100644 index 0000000..bb75d7d --- /dev/null +++ b/src/main/java/de/hitec/nhplus/fixtures/UserFixture.java @@ -0,0 +1,108 @@ +package de.hitec.nhplus.fixtures; + +import de.hitec.nhplus.Main; +import de.hitec.nhplus.datastorage.DaoFactory; +import de.hitec.nhplus.login.Permissions; +import de.hitec.nhplus.login.User; +import de.hitec.nhplus.login.database.UserDao; +import de.hitec.nhplus.nurse.Nurse; + +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.sql.Connection; +import java.sql.SQLException; +import java.util.*; + +/** + * {@link Fixture} for {@link User}. + * + * @author Dominik Säume + */ +public class UserFixture implements Fixture { + private static final String SCHEMA = "/de/hitec/nhplus/login/database/User.sql"; + private static final String PERMISSION_SCHEMA = "/de/hitec/nhplus/login/database/UserPermission.sql"; + private static final String TO_NURSE_SCHEMA = "/de/hitec/nhplus/login/database/UserToNurse.sql"; + + private final Map nursesByName; + + public UserFixture(Map nursesByName) { + this.nursesByName = nursesByName; + } + + @Override + public void dropTable(Connection connection) throws SQLException { + connection.createStatement().execute("DROP TABLE IF EXISTS user"); + connection.createStatement().execute("DROP TABLE IF EXISTS user__permissions"); + connection.createStatement().execute("DROP TABLE IF EXISTS user__nurse"); + } + + @Override + public void setupTable(Connection connection) throws SQLException { + final InputStream schema = Main.class.getResourceAsStream(SCHEMA); + final InputStream permissionSchema = Main.class.getResourceAsStream(PERMISSION_SCHEMA); + final InputStream toNurseSchema = Main.class.getResourceAsStream(TO_NURSE_SCHEMA); + + assert schema != null; + assert permissionSchema != null; + assert toNurseSchema != null; + + String SQL = new Scanner(schema, StandardCharsets.UTF_8) + .useDelimiter("\\A") + .next(); + String permissionSQL = new Scanner(permissionSchema, StandardCharsets.UTF_8) + .useDelimiter("\\A") + .next(); + String toNurseSQL = new Scanner(toNurseSchema, StandardCharsets.UTF_8) + .useDelimiter("\\A") + .next(); + + connection.createStatement().execute(SQL); + connection.createStatement().execute(permissionSQL); + connection.createStatement().execute(toNurseSQL); + } + + @Override + public Map load() throws SQLException { + List users = new ArrayList<>(); + + User udo = new User( + "udo", + Permissions.OWNER, + null + ); + udo.setPassword("uD0_187!"); + users.add(udo); + + User maria = new User( + "maria", + Permissions.NURSE, + nursesByName.get("Maria") + ); + maria.setPassword("H!mm3lf4hrt"); + users.add(maria); + + User werner = new User( + "werner", + Permissions.MANAGEMENT, + null + ); + werner.setPassword("TurboSchraube42!"); + users.add(werner); + + User fullPermissionsTestUser = new User( + "test", + -1, + null + ); + fullPermissionsTestUser.setPassword(""); + users.add(fullPermissionsTestUser); + + UserDao dao = DaoFactory.getInstance().createUserDAO(); + Map usersByUsername = new HashMap<>(); + for (User user : users) { + dao.create(user); + usersByUsername.put(user.getUsername(), user); + } + return usersByUsername; + } +} diff --git a/src/main/java/de/hitec/nhplus/login/LoginController.java b/src/main/java/de/hitec/nhplus/login/LoginController.java new file mode 100644 index 0000000..29cad23 --- /dev/null +++ b/src/main/java/de/hitec/nhplus/login/LoginController.java @@ -0,0 +1,104 @@ +package de.hitec.nhplus.login; + +import de.hitec.nhplus.datastorage.DaoFactory; +import de.hitec.nhplus.login.database.UserDao; +import javafx.animation.PauseTransition; +import javafx.animation.TranslateTransition; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.PasswordField; +import javafx.scene.control.TextField; +import javafx.stage.Stage; +import javafx.util.Duration; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.sql.SQLException; +import java.util.Arrays; + +/** + * Controller for handling the login of {@link User}s. + * + * @author Dominik Säume + */ +public class LoginController { + + public User user; + @FXML + public TextField textFieldUsername; + @FXML + public PasswordField passwordField; + @FXML + public Button buttonSubmit; + private Stage stage; + private int loginTries = 0; + + /** + * JavaFX Initialization method that is called after the binding of all the fields. + * + * @param stage The {@link Stage}, so the modal can close itself, when finished. + */ + public void initialize(Stage stage) { + this.stage = stage; + } + + /** + * Internal method to handle actions on wrong logins, like a shake, timout and total tries. + */ + private void handleWrongPasswordOrUsername() { + loginTries++; + + // Shake + TranslateTransition ttUsername = new TranslateTransition(Duration.millis(50), textFieldUsername); + ttUsername.setByX(10); + ttUsername.setAutoReverse(true); + ttUsername.setCycleCount(6); + + TranslateTransition ttPassword = new TranslateTransition(Duration.millis(50), passwordField); + ttPassword.setByX(10); + ttPassword.setAutoReverse(true); + ttPassword.setCycleCount(6); + + ttUsername.play(); + ttPassword.play(); + + // Timout + PauseTransition pause = new PauseTransition(Duration.seconds(3)); + pause.setOnFinished(event -> { + if (loginTries == 3) { + stage.close(); + } + buttonSubmit.setDisable(false); + }); + pause.play(); + + } + + @FXML + public void handleSubmit() { + buttonSubmit.setDisable(true); + + UserDao dao = DaoFactory.getInstance().createUserDAO(); + try { + int id = dao.readUserId(textFieldUsername.getText()); + if (id == 0) { + handleWrongPasswordOrUsername(); + return; + } + byte[] salt = dao.readPasswordSalt(id); + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(salt); + byte[] hash = md.digest(passwordField.getText().getBytes(StandardCharsets.UTF_8)); + byte[] requiredHash = dao.readPasswordHash(id); + if (!Arrays.equals(hash, requiredHash)) { + handleWrongPasswordOrUsername(); + return; + } + user = dao.read(id); + stage.close(); + } catch (SQLException | NoSuchAlgorithmException exception) { + exception.printStackTrace(); + } + } +} diff --git a/src/main/java/de/hitec/nhplus/login/Permissions.java b/src/main/java/de/hitec/nhplus/login/Permissions.java new file mode 100644 index 0000000..2f53369 --- /dev/null +++ b/src/main/java/de/hitec/nhplus/login/Permissions.java @@ -0,0 +1,14 @@ +package de.hitec.nhplus.login; + +/** + * A simple class holding the bitmasks for all permissions. + * This is a class instead of an enum, for ease of use. + * + * @author Dominiok Säume + */ +public class Permissions { + public final static int EVERYBODY = 0b0; + public final static int NURSE = 0b1; + public final static int MANAGEMENT = 0b10; + public final static int OWNER = 0b100; +} diff --git a/src/main/java/de/hitec/nhplus/login/User.java b/src/main/java/de/hitec/nhplus/login/User.java new file mode 100644 index 0000000..b58e6f2 --- /dev/null +++ b/src/main/java/de/hitec/nhplus/login/User.java @@ -0,0 +1,120 @@ +package de.hitec.nhplus.login; + +import de.hitec.nhplus.nurse.Nurse; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; + + +/** + * The model for a {@link User}. + * + * @author Dominik Säume + */ +public class User { + + private int id; + private String username; + private byte[] passwordSalt; + private byte[] passwordHash; + private int permissions = 0; + private Nurse nurse; + + + /** + * This constructor allows instantiating a {@link User} object with all existing fields. + */ + public User( + int id, + String username, + byte[] passwordSalt, + byte[] passwordHash, + int permissions, + Nurse nurse + ) { + this.id = id; + this.username = username; + this.passwordSalt = passwordSalt; + this.passwordHash = passwordHash; + this.permissions = permissions; + this.nurse = nurse; + } + + /** + * This constructor allows instantiating a {@link User} object. + */ + public User( + String username, + int permissions, + Nurse nurse + ) { + this.username = username; + this.permissions = permissions; + this.nurse = nurse; + } + + /** + * Sets the {@link User} password. The {@link User} will need to be manually stored with the + * {@link de.hitec.nhplus.login.database.UserDao UserDao}, for changes to persists. + * @param password The new Password + */ + public void setPassword(String password) { + try { + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[32]; + random.nextBytes(salt); + this.passwordSalt = salt; + MessageDigest md = MessageDigest.getInstance("SHA-512"); + md.update(salt); + this.passwordHash = md.digest(password.getBytes(StandardCharsets.UTF_8)); + }catch (NoSuchAlgorithmException exception){ + exception.printStackTrace(); + } + } + + public boolean hasNursePermissions(){ + return (permissions & Permissions.NURSE) != 0; + } + + public boolean hasAdminPermissions(){ + return (permissions & Permissions.MANAGEMENT) != 0; + } + + public int getId() { + return id; + } + + public byte[] getPasswordSalt() { + return passwordSalt; + } + + public byte[] getPasswordHash() { + return passwordHash; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public int getPermissions() { + return permissions; + } + + public void setPermissions(int permissions) { + this.permissions = permissions; + } + + public Nurse getNurse() { + return nurse; + } + + public void setNurse(Nurse nurse) { + this.nurse = nurse; + } +} diff --git a/src/main/java/de/hitec/nhplus/login/database/UserDao.java b/src/main/java/de/hitec/nhplus/login/database/UserDao.java new file mode 100644 index 0000000..221d36a --- /dev/null +++ b/src/main/java/de/hitec/nhplus/login/database/UserDao.java @@ -0,0 +1,232 @@ +package de.hitec.nhplus.login.database; + +import de.hitec.nhplus.datastorage.Dao; +import de.hitec.nhplus.datastorage.DaoFactory; +import de.hitec.nhplus.login.User; +import de.hitec.nhplus.nurse.Nurse; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link UserDao} is an implementation of the{@link de.hitec.nhplus.datastorage.Dao Dao} + * for the {@link User} model. + * + * @author Dominik Säume + */ +public class UserDao implements Dao { + protected final Connection connection; + + public UserDao(Connection connection) { + this.connection = connection; + } + + /** + * Try reading a {@link User#id} by its {@link User#username}. + * @param username The {@link User#username username} to try reading the {@link User#id id} of. + * @return The {@link User#id}. {@code 0} if the {@link User} doesn't exist. + */ + public int readUserId(String username) throws SQLException { + final String SQL = "SELECT id FROM user WHERE username = ?"; + PreparedStatement statement = this.connection.prepareStatement(SQL); + statement.setString(1, username); + return statement.executeQuery().getInt(1); + } + + /** + * Read a {@link User}s {@link User#passwordSalt password salt}. + * @param id The {@link User#id} of which the {@link User#passwordSalt password salt} should be read. + * @return The {@link User#passwordSalt password salt} as a{@code byte[]}. + */ + public byte[] readPasswordSalt(int id) throws SQLException { + final String SQL = "SELECT passwordSalt FROM user WHERE id = ?"; + PreparedStatement statement = this.connection.prepareStatement(SQL); + statement.setInt(1, id); + return statement.executeQuery().getBytes(1); + } + + /** + * Read a {@link User}s {@link User#passwordHash password hash} for authentication purposes. + * @param id The {@link User#id} of which the {@link User#passwordHash password hash} should be read. + * @return The {@link User#passwordHash password hash} as a{@code byte[]}. + */ + public byte[] readPasswordHash(int id) throws SQLException { + final String SQL = "SELECT passwordHash FROM user WHERE id = ?"; + PreparedStatement statement = this.connection.prepareStatement(SQL); + statement.setInt(1, id); + return statement.executeQuery().getBytes(1); + } + + @Override + public User read(int id) throws SQLException { + final String SQL = """ + SELECT + user.username, + user.passwordSalt, + user.passwordHash, + user__permissions.permissions, + user__nurse.nurseId + FROM user + LEFT JOIN user__permissions ON user.id = user__permissions.userId + LEFT JOIN user__nurse ON user.id = user__nurse.userId + WHERE user.id = ?; + """; + PreparedStatement statement = this.connection.prepareStatement(SQL); + statement.setInt(1, id); + ResultSet result = statement.executeQuery(); + int nurseId = result.getInt(5); + Nurse nurse = null; + if (!result.wasNull()) { + nurse = DaoFactory.getInstance().createNurseDAO().read(nurseId); + } + return new User( + id, + result.getString(1), + result.getBytes(2), + result.getBytes(3), + result.getInt(4), + nurse + ); + } + + @Override + public void create(User user) throws SQLException { + connection.setAutoCommit(false); //Switch to Manual Commit, to do an SQL Transaction + final String userSQL = """ + INSERT INTO user + (username, passwordSalt, passwordHash) + VALUES (?, ?, ?); + """; + PreparedStatement statement = this.connection.prepareStatement(userSQL); + statement.setString(1, user.getUsername()); + statement.setBytes(2, user.getPasswordSalt()); + statement.setBytes(3, user.getPasswordHash()); + statement.execute(); + + ResultSet generatedKeys = connection.createStatement().executeQuery("SELECT last_insert_rowid()"); + connection.commit(); //Finish SQL Transaction + connection.setAutoCommit(true); //Switch back Mode + + if (!generatedKeys.next()) { + return; + } + int newId = generatedKeys.getInt(1); + + final String permissionSQL = """ + INSERT INTO user__permissions + (userId, permissions) + VALUES (?, ?); + """; + PreparedStatement permissionStatement = this.connection.prepareStatement(permissionSQL); + permissionStatement.setInt(1, newId); + permissionStatement.setInt(2, user.getPermissions()); + permissionStatement.execute(); + + if (user.getNurse() == null) { + return; + } + final String nurseSQL = """ + INSERT INTO user__nurse + (userId, nurseId) + VALUES (?, ?); + """; + PreparedStatement nurseStatement = this.connection.prepareStatement(nurseSQL); + nurseStatement.setInt(1, newId); + nurseStatement.setInt(2, user.getNurse().getId()); + nurseStatement.execute(); + } + + @Override + public void update(User user) throws SQLException { + final String userSQL = """ + UPDATE user SET + username = ?, + passwordSalt = ?, + passwordHash = ? + WHERE id = ? + """; + PreparedStatement statement = this.connection.prepareStatement(userSQL); + statement.setString(1, user.getUsername()); + statement.setBytes(2, user.getPasswordSalt()); + statement.setBytes(3, user.getPasswordHash()); + statement.setInt(3, user.getId()); + statement.executeUpdate(); + + final String permissionSQL = """ + UPDATE user__permissions SET + permissions = ? + WHERE userId = ? + """; + PreparedStatement permissionStatement = this.connection.prepareStatement(permissionSQL); + permissionStatement.setInt(1, user.getPermissions()); + permissionStatement.setInt(2, user.getId()); + permissionStatement.executeUpdate(); + + if (user.getNurse() == null) { + final String nurseSQL = """ + DELETE FROM user__nurse WHERE userId = ? + """; + this.connection.prepareStatement(nurseSQL).executeUpdate(); + return; + } + + final String nurseSQL = """ + UPDATE user__nurse set + nurseId = ? + WHERE userId = ? + """; + PreparedStatement nurseStatement = this.connection.prepareStatement(nurseSQL); + nurseStatement.setInt(1, user.getNurse().getId()); + nurseStatement.setInt(2, user.getId()); + permissionStatement.executeUpdate(); + } + + @Override + public void delete(int id) throws SQLException { + final String SQL = """ + DELETE FROM user WHERE user.id = ?; + """; + PreparedStatement preparedStatement = this.connection.prepareStatement(SQL); + preparedStatement.setInt(1, id); + preparedStatement.executeUpdate(); + } + + @Override + public List readAll() throws SQLException { + final String SQL = """ + SELECT + user.id, + user.username, + user.passwordSalt, + user.passwordHash, + user__permissions.permissions, + user__nurse.nurseId + FROM user + LEFT JOIN user__permissions ON user.id = user__permissions.userId + LEFT JOIN user__nurse ON user.id = user__nurse.userId + """; + ResultSet result = connection.prepareStatement(SQL).executeQuery(); + + List users = new ArrayList<>(); + while (result.next()) { + int nurseId = result.getInt(6); + Nurse nurse = null; + if (!result.wasNull()) { + nurse = DaoFactory.getInstance().createNurseDAO().read(nurseId); + } + users.add(new User( + result.getInt(1), + result.getString(2), + result.getBytes(3), + result.getBytes(4), + result.getInt(5), + nurse + )); + } + return users; + } +} diff --git a/src/main/java/de/hitec/nhplus/main/MainWindowController.java b/src/main/java/de/hitec/nhplus/main/MainWindowController.java index 5b7b4e4..c402020 100644 --- a/src/main/java/de/hitec/nhplus/main/MainWindowController.java +++ b/src/main/java/de/hitec/nhplus/main/MainWindowController.java @@ -1,224 +1,98 @@ package de.hitec.nhplus.main; -import de.hitec.nhplus.Main; -import de.hitec.nhplus.nurse.Nurse; +import de.hitec.nhplus.login.Permissions; +import de.hitec.nhplus.login.User; +import de.hitec.nhplus.utils.tab.TabManager; +import de.hitec.nhplus.utils.tab.TabStruct; +import javafx.event.Event; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; -import javafx.scene.control.SelectionModel; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.BorderPane; -import de.hitec.nhplus.treatment.Treatment; -import de.hitec.nhplus.medication.Medication; -import de.hitec.nhplus.patient.Patient; -import java.io.IOException; -import java.util.Objects; +import java.util.List; /** * Controller for the main window of the application, which holds all tabs. * - * @author Bernd Heidemann * @author Dominik Säume - * @author Armin Ribic - * @author Dorian Nemec */ public class MainWindowController { + private static MainWindowController instace; @FXML - private TabPane mainTabPane; - @FXML - private AnchorPane patientPage; - @FXML - private Tab patientTab; - @FXML - private AnchorPane activeTreatmentPage; - @FXML - private Tab treatmentTab; - @FXML - private TabPane treatmentTabPane; - @FXML - private Tab activeTreatmentTab; - @FXML - private AnchorPane lockedTreatmentPage; - @FXML - private Tab lockedTreatmentTab; - @FXML - private Tab nurseTab; - @FXML - private TabPane nurseTabPane; - @FXML - private AnchorPane activeNursePage; - @FXML - private Tab activeNurseTab; - @FXML - private AnchorPane lockedNursePage; - @FXML - private Tab lockedNurseTab; - @FXML - private AnchorPane medicationPage; - @FXML - private Tab medicationTab; - - /** - * Initialization method that is called after the binding of all the fields. - */ - @FXML - public void initialize() { - loadPatientPage(); - mainTabPane.getSelectionModel().select(patientTab); - - patientTab.setOnSelectionChanged(event -> loadPatientPage()); - medicationTab.setOnSelectionChanged(event -> loadMedicationPage()); - - - nurseTab.setOnSelectionChanged(event -> loadNursePage()); - nurseTabPane.getSelectionModel().select(activeNurseTab); - - activeNurseTab.setOnSelectionChanged(event -> loadActiveNursePage()); - lockedNurseTab.setOnSelectionChanged(event -> loadLockedNursePage()); - - treatmentTab.setOnSelectionChanged(event -> loadTreatmentPage()); - treatmentTabPane.getSelectionModel().select(activeTreatmentTab); - - activeTreatmentTab.setOnSelectionChanged(event -> loadActiveTreatmentPage()); - lockedTreatmentTab.setOnSelectionChanged(event -> loadLockedTreatmentPage()); + public TabPane mainTabPane; + private User user; + private TabManager tabManager; + public MainWindowController() { + instace = this; } /** - * Loads the {@link Patient} page into its tab. + * Method copying the idea of a singleton, to allow getting the current {@link User}. */ - private void loadPatientPage() { - try { - BorderPane patientsPane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/patient/AllPatientView.fxml")) - ); - patientPage.getChildren().setAll(patientsPane); - AnchorPane.setTopAnchor(patientsPane, 0d); - AnchorPane.setBottomAnchor(patientsPane, 0d); - AnchorPane.setLeftAnchor(patientsPane, 0d); - AnchorPane.setRightAnchor(patientsPane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } + public static MainWindowController getInstance() { + return instace; } /** - * Loads the {@link Treatment } tab. + * JavaFX Initialization method that is called after the binding of all the fields. + * + * @param user The logged in {@link User}. */ - private void loadTreatmentPage() { - SelectionModel selectionModel = treatmentTabPane.getSelectionModel(); - Tab selectedTab = selectionModel.getSelectedItem(); - if (selectedTab == activeTreatmentTab) { - loadActiveTreatmentPage(); - } - if (selectedTab == lockedTreatmentTab) { - loadLockedTreatmentPage(); - } + @FXML + public void initialize(User user) { + instace = this; + this.user = user; + this.tabManager = new TabManager(user); + setupTabs(); } /** - * Loads the active {@link Treatment} page into its tab. + * Sets up all Tabs Accessible, with help of the {@link TabManager}. */ - private void loadActiveTreatmentPage() { - try { - BorderPane activeTreatmentPane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/treatment/AllTreatmentView.fxml")) - ); - activeTreatmentPage.getChildren().setAll(activeTreatmentPane); - AnchorPane.setTopAnchor(activeTreatmentPane, 0d); - AnchorPane.setBottomAnchor(activeTreatmentPane, 0d); - AnchorPane.setLeftAnchor(activeTreatmentPane, 0d); - AnchorPane.setRightAnchor(activeTreatmentPane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } + private void setupTabs() { + tabManager.setupTab(mainTabPane, new TabStruct( + "Patienten", + "/de/hitec/nhplus/patient/AllPatientView.fxml", + Permissions.NURSE + )); + tabManager.setupSubTabPane(mainTabPane, "Behandlungen", Permissions.NURSE, List.of( + new TabStruct( + "Behandlungen", + "/de/hitec/nhplus/treatment/AllTreatmentView.fxml", + Permissions.NURSE + ), + new TabStruct( + "Gesperrte Behandlungen", + "/de/hitec/nhplus/treatment/LockedTreatmentView.fxml", + Permissions.NURSE + ) + )); + tabManager.setupSubTabPane(mainTabPane, "Pfleger", Permissions.EVERYBODY, List.of( + new TabStruct( + "Pfleger", + "/de/hitec/nhplus/nurse/AllNurseView.fxml", + Permissions.NURSE | Permissions.MANAGEMENT | Permissions.OWNER + ), + new TabStruct( + "Gesperrte Pfleger", + "/de/hitec/nhplus/nurse/LockedNurseView.fxml", + Permissions.MANAGEMENT | Permissions.OWNER + ) + )); + tabManager.setupTab(mainTabPane, new TabStruct( + "Medikamente", + "/de/hitec/nhplus/medication/AllMedicationView.fxml", + Permissions.MANAGEMENT + )); + + + Tab defaultTab = mainTabPane.getTabs().get(0); + mainTabPane.getSelectionModel().select(defaultTab); + defaultTab.getOnSelectionChanged().handle(new Event(Event.ANY)); } - /** - * Loads the locked {@link Treatment} page into its tab. - */ - private void loadLockedTreatmentPage() { - try { - BorderPane treatmentsPane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/treatment/LockedTreatmentView.fxml")) - ); - lockedTreatmentPage.getChildren().setAll(treatmentsPane); - AnchorPane.setTopAnchor(treatmentsPane, 0d); - AnchorPane.setBottomAnchor(treatmentsPane, 0d); - AnchorPane.setLeftAnchor(treatmentsPane, 0d); - AnchorPane.setRightAnchor(treatmentsPane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - /** - * Loads the {@link Nurse} page into its tab. - */ - private void loadNursePage() { - SelectionModel selectionModel = nurseTabPane.getSelectionModel(); - Tab selectedTab = selectionModel.getSelectedItem(); - if (selectedTab == activeNurseTab) { - loadActiveNursePage(); - } - if (selectedTab == lockedNurseTab) { - loadLockedNursePage(); - } - } - - /** - * Loads the active {@link Nurse} page into its tab. - */ - private void loadActiveNursePage() { - try { - BorderPane activeNursePane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/nurse/AllNurseView.fxml")) - ); - activeNursePage.getChildren().setAll(activeNursePane); - AnchorPane.setTopAnchor(activeNursePane, 0d); - AnchorPane.setBottomAnchor(activeNursePane, 0d); - AnchorPane.setLeftAnchor(activeNursePane, 0d); - AnchorPane.setRightAnchor(activeNursePane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - /** - * Loads the locked {@link Nurse} page into its tab. - */ - private void loadLockedNursePage() { - try { - BorderPane lockedNursePane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/nurse/LockedNurseView.fxml")) - ); - lockedNursePage.getChildren().setAll(lockedNursePane); - AnchorPane.setTopAnchor(lockedNursePane, 0d); - AnchorPane.setBottomAnchor(lockedNursePane, 0d); - AnchorPane.setLeftAnchor(lockedNursePane, 0d); - AnchorPane.setRightAnchor(lockedNursePane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } - } - - /** - * Loads the {@link Medication} page into its tab. - */ - private void loadMedicationPage() { - try { - BorderPane medicationPane = FXMLLoader.load( - Objects.requireNonNull(Main.class.getResource("/de/hitec/nhplus/medication/AllMedicationView.fxml")) - ); - medicationPage.getChildren().setAll(medicationPane); - AnchorPane.setTopAnchor(medicationPane, 0d); - AnchorPane.setBottomAnchor(medicationPane, 0d); - AnchorPane.setLeftAnchor(medicationPane, 0d); - AnchorPane.setRightAnchor(medicationPane, 0d); - } catch (IOException exception) { - exception.printStackTrace(); - } + public User getUser() { + return user; } } diff --git a/src/main/java/de/hitec/nhplus/nurse/AllNurseController.java b/src/main/java/de/hitec/nhplus/nurse/AllNurseController.java index 6f72582..cf9dfa2 100644 --- a/src/main/java/de/hitec/nhplus/nurse/AllNurseController.java +++ b/src/main/java/de/hitec/nhplus/nurse/AllNurseController.java @@ -1,8 +1,8 @@ package de.hitec.nhplus.nurse; -import static de.hitec.nhplus.utils.Validator.*; - import de.hitec.nhplus.datastorage.DaoFactory; +import de.hitec.nhplus.login.Permissions; +import de.hitec.nhplus.main.MainWindowController; import de.hitec.nhplus.nurse.database.NurseDao; import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; @@ -17,6 +17,8 @@ import javafx.scene.control.cell.TextFieldTableCell; import java.sql.SQLException; +import static de.hitec.nhplus.utils.Validator.*; + /** * The controller for viewing all {@link Nurse}s. * @@ -49,12 +51,17 @@ public class AllNurseController { private final ObservableList nurses = FXCollections.observableArrayList(); private NurseDao dao; + private boolean hasEditPermissions; /** * Initialization method that is called after the binding of all the fields. */ @FXML public void initialize() { + int editPermissions = Permissions.MANAGEMENT | Permissions.OWNER; + int userPermissions = MainWindowController.getInstance().getUser().getPermissions(); + hasEditPermissions = (userPermissions & editPermissions) != 0; + this.readAllAndShowInTableView(); this.columnId.setCellValueFactory(new PropertyValueFactory<>("id")); @@ -71,12 +78,18 @@ public class AllNurseController { this.tableView.setItems(this.nurses); + this.buttonAdd.setDisable(true); - ChangeListener inputNewNurseValidationListener = (observableValue, oldText, newText)-> + if (!hasEditPermissions) { + this.buttonLock.setDisable(true); + return; + } + + ChangeListener inputNewNurseValidationListener = (observableValue, oldText, newText) -> { boolean isValid = isValidFirstName(this.textFieldFirstName.getText()) - && isValidSurName(this.textFieldSurName.getText()) - && isValidPhoneNumber(this.textFieldPhoneNumber.getText()); + && isValidSurName(this.textFieldSurName.getText()) + && isValidPhoneNumber(this.textFieldPhoneNumber.getText()); AllNurseController.this.buttonAdd.setDisable(!isValid); }; @@ -89,12 +102,12 @@ public class AllNurseController { /** * Internal method to read all data and set it to the table view. */ - private void readAllAndShowInTableView(){ + private void readAllAndShowInTableView() { this.nurses.clear(); this.dao = DaoFactory.getInstance().createNurseDAO(); try { this.nurses.setAll(this.dao.readAllActive()); - }catch (SQLException exception){ + } catch (SQLException exception) { exception.printStackTrace(); } } @@ -120,14 +133,13 @@ public class AllNurseController { } @FXML - public void handleAdd(){ - String surname=this.textFieldSurName.getText(); - String firstName=this.textFieldFirstName.getText(); - String phoneNumber=this.textFieldPhoneNumber.getText(); + public void handleAdd() { + String surname = this.textFieldSurName.getText(); + String firstName = this.textFieldFirstName.getText(); + String phoneNumber = this.textFieldPhoneNumber.getText(); try { this.dao.create(new Nurse(firstName, surname, phoneNumber)); - } - catch (SQLException exception){ + } catch (SQLException exception) { exception.printStackTrace(); } readAllAndShowInTableView(); @@ -135,16 +147,16 @@ public class AllNurseController { } @FXML - public void handleLock(){ + public void handleLock() { Nurse selectedItem = this.tableView.getSelectionModel().getSelectedItem(); - if (selectedItem == null){ + if (selectedItem == null) { return; } try { selectedItem.setLocked(true); this.dao.update(selectedItem); - }catch (SQLException exception){ + } catch (SQLException exception) { exception.printStackTrace(); } readAllAndShowInTableView(); @@ -152,6 +164,10 @@ public class AllNurseController { @FXML public void handleOnEditSurname(TableColumn.CellEditEvent event) { + if(!hasEditPermissions){ + event.getTableView().refresh(); + return; + } String newSurName = event.getNewValue(); if (!isValidSurName(newSurName)) { showValidationError("Nachname"); @@ -164,6 +180,10 @@ public class AllNurseController { @FXML public void handleOnEditFirstname(TableColumn.CellEditEvent event) { + if(!hasEditPermissions){ + event.getTableView().refresh(); + return; + } String newFirstName = event.getNewValue(); if (!isValidFirstName(newFirstName)) { showValidationError("Vorname"); @@ -176,6 +196,10 @@ public class AllNurseController { @FXML public void handleOnEditPhoneNumber(TableColumn.CellEditEvent event) { + if(!hasEditPermissions){ + event.getTableView().refresh(); + return; + } String newPhoneNumber = event.getNewValue(); if (!isValidPhoneNumber(newPhoneNumber)) { showValidationError("Telefonnummer"); diff --git a/src/main/java/de/hitec/nhplus/nurse/Nurse.java b/src/main/java/de/hitec/nhplus/nurse/Nurse.java index 22c4b49..7fa94b9 100644 --- a/src/main/java/de/hitec/nhplus/nurse/Nurse.java +++ b/src/main/java/de/hitec/nhplus/nurse/Nurse.java @@ -44,7 +44,7 @@ public class Nurse extends Person { /** * This constructor allows instantiating a {@link Nurse} object with - * specifying if the nurse is locked or not. + * specifying whether the {@link Nurse} is locked or not. */ public Nurse( String firstName, diff --git a/src/main/java/de/hitec/nhplus/utils/tab/TabManager.java b/src/main/java/de/hitec/nhplus/utils/tab/TabManager.java new file mode 100644 index 0000000..7a872a8 --- /dev/null +++ b/src/main/java/de/hitec/nhplus/utils/tab/TabManager.java @@ -0,0 +1,114 @@ +package de.hitec.nhplus.utils.tab; + +import de.hitec.nhplus.Main; +import de.hitec.nhplus.login.Permissions; +import de.hitec.nhplus.login.User; +import javafx.event.Event; +import javafx.event.EventHandler; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.BorderPane; + +import java.io.IOException; +import java.util.List; +import java.util.Objects; + +/** + * A utility class to construct {@link Tab}s from views. This also supports sub {@link Tab}s. + * + * @author Dominiok Säume + * @see TabManager + */ +public class TabManager { + private final User user; + + public TabManager(User user) { + this.user = user; + } + + /** + * Internal method to check permissions + * @param requiredPermissions The permissions the {@link User} requires. + * @param permissions The permissions the {@link User} has. + */ + private boolean hasPermissions(int requiredPermissions, int permissions) { + return requiredPermissions == Permissions.EVERYBODY + || (permissions & requiredPermissions) != 0; + } + + /** + * Method to set up a single tab and register it to its {@link TabPane}. + * @param tabPane The {@link TabPane} to register to. + * @param tabStruct The data needed to construct the new tab. + */ + public void setupTab(TabPane tabPane, TabStruct tabStruct) { + if (!hasPermissions(tabStruct.requiredPermissions, user.getPermissions())) { + return; + } + Tab tab = new Tab(); + tab.setText(tabStruct.title); + AnchorPane pane = new AnchorPane(); + tab.setContent(pane); + tab.setOnSelectionChanged(loadTabEventHandler(tabStruct, pane)); + tabPane.getTabs().add(tab); + } + + /** + * {@link EventHandler} to handle the loading of a {@link Tab}. + * @param tabStruct The {@link TabStruct} holding the path of the view. + * @param pane The main pane of the {@link Tab}. + */ + private EventHandler loadTabEventHandler(TabStruct tabStruct, AnchorPane pane) { + return event -> { + try { + BorderPane patientsPane = FXMLLoader.load( + Objects.requireNonNull(Main.class.getResource(tabStruct.view)) + ); + pane.getChildren().setAll(patientsPane); + AnchorPane.setTopAnchor(patientsPane, 0d); + AnchorPane.setBottomAnchor(patientsPane, 0d); + AnchorPane.setLeftAnchor(patientsPane, 0d); + AnchorPane.setRightAnchor(patientsPane, 0d); + } catch (IOException exception) { + exception.printStackTrace(); + } + }; + } + + /** + * Method to set up a sub tab and register it to its parent {@link TabPane}. + * @param parentTabPane The {@link TabPane} to register to. + * @param title The title of the {@link Tab}. + * @param requiredPermissions The permissions required to see the sub {@link Tab}. + * @param subTabs A {@link List} of {@link TabStruct}s to create in the sub {@link Tab}. + */ + public void setupSubTabPane(TabPane parentTabPane, String title, int requiredPermissions, List subTabs) { + if (!hasPermissions(requiredPermissions, user.getPermissions())) { + return; + } + Tab tab = new Tab(); + tab.setText(title); + TabPane subTabPane = new TabPane(); + subTabPane.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); + tab.setContent(subTabPane); + subTabs.forEach(tabStruct -> setupTab(subTabPane, tabStruct)); + if (subTabPane.getTabs().isEmpty()) { + return; + } + tab.setOnSelectionChanged(loadSubTabPaneEventHandler(subTabPane)); + parentTabPane.getTabs().add(tab); + } + + /** + * {@link EventHandler} to handle the loading of a sub {@link TabPane}. + * @param tabPane The {@link TabPane} of the sub {@link Tab} to handle loading. + */ + private EventHandler loadSubTabPaneEventHandler(TabPane tabPane) { + return event -> { + Tab tab = tabPane.getSelectionModel().getSelectedItem(); + tab.getOnSelectionChanged().handle(new Event(Event.ANY)); + }; + } +} diff --git a/src/main/java/de/hitec/nhplus/utils/tab/TabStruct.java b/src/main/java/de/hitec/nhplus/utils/tab/TabStruct.java new file mode 100644 index 0000000..cc2244e --- /dev/null +++ b/src/main/java/de/hitec/nhplus/utils/tab/TabStruct.java @@ -0,0 +1,19 @@ +package de.hitec.nhplus.utils.tab; + +/** + * A simple class holding the data needed for constructing a {@link javafx.scene.control.Tab Tab}. + * + * @author Dominiok Säume + * @see TabManager + */ +public class TabStruct { + public String title; + public String view; + public int requiredPermissions; + + public TabStruct(String title, String view, int requiredPermissions) { + this.title = title; + this.view = view; + this.requiredPermissions = requiredPermissions; + } +} diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 06e67a7..56fcea2 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -10,6 +10,9 @@ module de.hitec.nhplus { exports de.hitec.nhplus.main; opens de.hitec.nhplus.main to javafx.base, javafx.fxml; + exports de.hitec.nhplus.login; + opens de.hitec.nhplus.login to javafx.base, javafx.fxml; + exports de.hitec.nhplus.patient; exports de.hitec.nhplus.patient.database; opens de.hitec.nhplus.patient.database to javafx.base, javafx.fxml; diff --git a/src/main/resources/de/hitec/nhplus/login/LoginView.fxml b/src/main/resources/de/hitec/nhplus/login/LoginView.fxml new file mode 100644 index 0000000..234ff7b --- /dev/null +++ b/src/main/resources/de/hitec/nhplus/login/LoginView.fxml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + +
+ + + + + + + +
+ +