package org.peerbox.presenter;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.PasswordField;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import org.hive2hive.core.exceptions.NoPeerConnectionException;
import org.peerbox.ResultStatus;
import org.peerbox.app.manager.user.IUserManager;
import org.peerbox.presenter.validation.CombinedPasswordValidator;
import org.peerbox.presenter.validation.CombinedPinValidator;
import org.peerbox.presenter.validation.UsernameValidator;
import org.peerbox.presenter.validation.ValidationUtils.ValidationResult;
import org.peerbox.view.ViewNames;
import org.peerbox.view.controls.ErrorLabel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Inject;
/**
* Controller for user registration form.
* @author albrecht
*
*/
public class RegisterController implements Initializable {
private static final Logger logger = LoggerFactory.getLogger(RegisterController.class);
private NavigationService fNavigationService;
private IUserManager fUserManager;
@FXML
private TextField txtUsername;
@FXML
private Label lblUsernameError;
@FXML
private PasswordField txtPassword_1;
@FXML
private Label lblPasswordError;
@FXML
private PasswordField txtPassword_2;
@FXML
private PasswordField txtPin_1;
@FXML
private Label lblPinError;
@FXML
private PasswordField txtPin_2;
@FXML
private Button btnRegister;
@FXML
private Button btnBack;
@FXML
private GridPane grdForm;
@FXML
private ProgressIndicator piProgress;
@FXML
private ErrorLabel lblError;
private UsernameValidator usernameValidator;
private CombinedPasswordValidator passwordValidator;
private CombinedPinValidator pinValidator;
@Inject
public RegisterController(NavigationService navigationService, IUserManager userManager) {
fNavigationService = navigationService;
fUserManager = userManager;
}
public void initialize(URL location, ResourceBundle resources) {
initializeValidations();
}
/**
* Resets the controller to an initial state
*/
private void resetForm() {
txtUsername.clear();
txtPassword_1.clear();
txtPassword_2.clear();
txtPin_1.clear();
txtPin_2.clear();
grdForm.disableProperty().unbind();
grdForm.setDisable(false);
uninstallProgressIndicator();
uninstallValidationDecorations();
}
/**
* Installs decorators for field validation
*/
private void initializeValidations() {
usernameValidator = new UsernameValidator(txtUsername, lblUsernameError.textProperty(), fUserManager);
passwordValidator = new CombinedPasswordValidator(txtPassword_1, lblPasswordError.textProperty(), txtPassword_2);
pinValidator = new CombinedPinValidator(txtPin_1, lblPinError.textProperty(), txtPin_2);
}
/**
* Remove the decorations that are installed during validation
*/
private void uninstallValidationDecorations() {
usernameValidator.reset();
passwordValidator.reset();
pinValidator.reset();
}
/**
* Register new user action. Validates user input (on demand) first
* @param event that was fired.
*/
@FXML
public void registerAction(ActionEvent event) {
clearError();
if (!validateAll().isError()) {
Task<ResultStatus> task = createRegisterTask();
new Thread(task).start();
}
}
/**
* Complete validation of all input fields AND'ed
* @return ANDed validation result
*/
private ValidationResult validateAll() {
// note, we want to evaluate ALL fields, regardless whether one validation fails or not.
// this way, all fields will be analyzed and marked if validation fails and not just the first
// field where validation fails.
// thus: use & and not &&
return (usernameValidator.validate(true) == ValidationResult.OK
& passwordValidator.validate() == ValidationResult.OK
& pinValidator.validate() == ValidationResult.OK)
? ValidationResult.OK : ValidationResult.ERROR;
}
/**
* Go back to previous page
* @param event that was fired.
*/
@FXML
public void navigateBackAction(ActionEvent event) {
logger.debug("Navigate back.");
fNavigationService.navigateBack();
}
/**
* Navigate to the login page.
* @param event that was fired.
*/
@FXML
public void loginAction(ActionEvent event) {
logger.debug("Navigate to Login page.");
fNavigationService.navigate(ViewNames.LOGIN_VIEW);
}
/**
* Registers a new user given the credentials.
* All checks regarding the input should happen before (e.g. is registered, password not empty, ...)
* @param username the user id
* @param password the password
* @param pin the pin
* @return status of the operation
*/
public ResultStatus registerUser(final String username, final String password, final String pin) {
try {
return fUserManager.registerUser(username, password, pin);
} catch (NoPeerConnectionException e) {
return ResultStatus.error("Could not register user because connection to network failed.");
}
}
/**
* Creates an asynchronous task that registers an user.
*
* @return result of operation
*/
private Task<ResultStatus> createRegisterTask() {
Task<ResultStatus> task = new Task<ResultStatus>() {
// credentials
final String username = getUsername();
final String password = txtPassword_1.getText();
final String pin = txtPin_1.getText();
@Override
public ResultStatus call() {
return registerUser(username, password, pin);
}
};
task.setOnScheduled(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
logger.info("Start registering new user profile");
installProgressIndicator();
grdForm.disableProperty().bind(task.runningProperty());
}
});
task.setOnFailed(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
onRegisterFailed(ResultStatus.error("Could not register user."));
}
});
task.setOnSucceeded(new EventHandler<WorkerStateEvent>() {
@Override
public void handle(WorkerStateEvent event) {
ResultStatus result = task.getValue();
if(result.isOk()) {
onRegisterSucceeded();
} else {
onRegisterFailed(result);
}
}
});
return task;
}
/**
* Callback for the async register task
*/
private void onRegisterSucceeded() {
logger.info("Registration task succeeded: user {} registered.", getUsername());
resetForm();
fNavigationService.navigate(ViewNames.LOGIN_VIEW);
}
/**
* Callback for the async register task
* @param result of the task
*/
private void onRegisterFailed(ResultStatus result) {
logger.error("Registration task failed: {}", result.getErrorMessage());
Platform.runLater(() -> {
uninstallProgressIndicator();
grdForm.disableProperty().unbind();
grdForm.requestLayout();
setError(result.getErrorMessage());
});
}
/**
* Shows a progress indicator
*/
private void installProgressIndicator() {
Platform.runLater(() -> {
// center indicator with respect to the grid
double xOffset = piProgress.getWidth() / 2.0;
double yOffset = piProgress.getHeight() / 2.0;
double x = grdForm.getWidth() / 2.0 - xOffset;
double y = grdForm.getHeight() / 2.0 - yOffset;
piProgress.relocate(x, y);
// show PI
piProgress.setVisible(true);
});
}
/**
* Hides the progress indicator
*/
private void uninstallProgressIndicator() {
Platform.runLater(() -> {
piProgress.setVisible(false);
});
}
/**
* Set an error text
* @param error message
*/
private void setError(String error) {
lblError.setText(error);
}
/**
* Reset the error text
*/
private void clearError() {
lblError.setText("");
}
/**
* Trimmed username of the text field
* @return username
*/
private String getUsername() {
return txtUsername.getText().trim();
}
}