Java tutorial
/* * Copyright 2015 Bekwam, Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.bekwam.resignator; import static java.util.Comparator.comparing; import static java.util.stream.Collectors.toList; import java.io.File; import java.io.IOException; import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Provider; import javax.inject.Singleton; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.bekwam.jfxbop.view.Viewable; import com.bekwam.resignator.commands.KeytoolCommand; import com.bekwam.resignator.commands.SignCommand; import com.bekwam.resignator.commands.UnsignCommand; import com.bekwam.resignator.model.ConfigurationDataSource; import com.bekwam.resignator.model.Profile; import com.bekwam.resignator.model.SigningArgumentsType; import com.google.common.base.Preconditions; import javafx.animation.FadeTransition; import javafx.application.Platform; import javafx.beans.InvalidationListener; import javafx.beans.WeakInvalidationListener; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.collections.FXCollections; import javafx.concurrent.Task; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.fxml.FXML; import javafx.scene.control.Alert; import javafx.scene.control.ButtonBar; import javafx.scene.control.ButtonType; import javafx.scene.control.CheckBox; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ChoiceBox; import javafx.scene.control.ChoiceDialog; import javafx.scene.control.Dialog; import javafx.scene.control.Hyperlink; import javafx.scene.control.Label; import javafx.scene.control.ListView; import javafx.scene.control.Menu; import javafx.scene.control.MenuItem; import javafx.scene.control.ProgressIndicator; import javafx.scene.control.SplitPane; import javafx.scene.control.TextArea; import javafx.scene.control.TextField; import javafx.scene.control.TextInputDialog; import javafx.scene.control.cell.TextFieldListCell; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.layout.FlowPane; import javafx.scene.layout.VBox; import javafx.stage.DirectoryChooser; import javafx.stage.FileChooser; import javafx.stage.Stage; import javafx.util.Duration; import javafx.util.StringConverter; /** * JavaFX Controller and JFXBop View for the Resignator App * * @author carl_000 * @since 1.0.0 */ @Viewable(fxml = "/fxml/ResignatorAppMainView.fxml", stylesheet = "/css/resignator.css", title = "ResignatorApp") @Singleton public class ResignatorAppMainViewController extends ResignatorBaseView { private final static Logger logger = LoggerFactory.getLogger(ResignatorAppMainViewController.class); public final BooleanProperty needsSave = new SimpleBooleanProperty(false); private final InvalidationListener needsSaveListener = (evt) -> needsSave.set(true); private final MenuItem MI_NO_PROFILES = new MenuItem("< None >"); private final int MAX_WAIT_TIME = 10 * 60 * 1000; // 10 minutes private final String SOURCE_LABEL_JAR = "Source JAR:"; private final String TARGET_LABEL_JAR = "Target JAR:"; private final String SOURCE_LABEL_FOLDER = "Source Folder:"; private final String TARGET_LABEL_FOLDER = "Target Folder:"; @FXML SplitPane sp; @FXML SplitPane outerSp; @FXML VBox console; @FXML VBox profileBrowser; @FXML TextField tfSourceFile; @FXML TextField tfTargetFile; @FXML Label lblStatus; @FXML ProgressIndicator piSignProgress; @FXML TextArea txtConsole; @FXML CheckBox ckReplace; @FXML MenuItem miSave; @FXML ListView<String> lvProfiles; @FXML Menu mRecentProfiles; @FXML MenuItem miHelp; @FXML ChoiceBox<SigningArgumentsType> cbType; @FXML Label lblSource; @FXML Label lblTarget; @Inject ConfigurationDataSource configurationDS; private final EventHandler<ActionEvent> recentProfileLoadHandler = (evt) -> doLoadProfile( ((MenuItem) evt.getSource()).getText()); @Inject @Named("ConfigFile") String configFile; @Inject Provider<SettingsController> settingsControllerProvider; @Inject Provider<JarsignerConfigController> jarsignerConfigControllerProvider; @Inject ActiveConfiguration activeConfiguration; @Inject ActiveProfile activeProfile; @Inject Provider<SignCommand> signCommandProvider; @Inject Provider<UnsignCommand> unsignCommandProvider; @Inject Provider<KeytoolCommand> keytoolCommandProvider; @Inject Provider<NewPasswordController> newPasswordControllerProvider; @Inject Provider<PasswordController> passwordControllerProvider; @Inject Provider<AboutController> aboutControllerProvider; @Inject @Named("NumRecentProfiles") Integer numRecentProfiles = 4; @Inject HelpDelegate helpDelegate; private String jarDir = System.getProperty("user.home"); @FXML public void initialize() { try { miHelp.setAccelerator(KeyCombination.keyCombination("F1")); cbType.getItems().add(SigningArgumentsType.JAR); cbType.getItems().add(SigningArgumentsType.FOLDER); cbType.getSelectionModel().select(SigningArgumentsType.JAR); cbType.setConverter(new StringConverter<SigningArgumentsType>() { @Override public String toString(SigningArgumentsType type) { return StringUtils.capitalize(StringUtils.lowerCase(String.valueOf(type))); } @Override public SigningArgumentsType fromString(String type) { return Enum.valueOf(SigningArgumentsType.class, StringUtils.upperCase(type)); } }); activeConfiguration.activeProfileProperty().bindBidirectional(activeProfile.profileNameProperty()); tfSourceFile.textProperty().bindBidirectional(activeProfile.sourceFileFileNameProperty()); tfTargetFile.textProperty().bindBidirectional(activeProfile.targetFileFileNameProperty()); ckReplace.selectedProperty().bindBidirectional(activeProfile.replaceSignaturesProperty()); cbType.valueProperty().bindBidirectional(activeProfile.argsTypeProperty()); miSave.disableProperty().bind(needsSave.not()); tfSourceFile.textProperty().addListener(new WeakInvalidationListener(needsSaveListener)); tfTargetFile.textProperty().addListener(new WeakInvalidationListener(needsSaveListener)); ckReplace.selectedProperty().addListener(new WeakInvalidationListener(needsSaveListener)); cbType.valueProperty().addListener(new WeakInvalidationListener(needsSaveListener)); lblSource.setText(SOURCE_LABEL_JAR); lblTarget.setText(TARGET_LABEL_JAR); cbType.getSelectionModel().selectedItemProperty().addListener((ov, old_v, new_v) -> { if (new_v == SigningArgumentsType.FOLDER) { if (!lblSource.getText().equalsIgnoreCase(SOURCE_LABEL_FOLDER)) { lblSource.setText(SOURCE_LABEL_FOLDER); } if (!lblSource.getText().equalsIgnoreCase(TARGET_LABEL_FOLDER)) { lblTarget.setText(TARGET_LABEL_FOLDER); } } else { if (!lblSource.getText().equalsIgnoreCase(SOURCE_LABEL_JAR)) { lblSource.setText(SOURCE_LABEL_JAR); } if (!lblSource.getText().equalsIgnoreCase(TARGET_LABEL_JAR)) { lblTarget.setText(TARGET_LABEL_JAR); } } }); lvProfiles.getSelectionModel().selectedItemProperty().addListener((ov, old_v, new_v) -> { if (new_v == null) { // coming from clearSelection or sort return; } if (needsSave.getValue()) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Discard unsaved profile?"); alert.setHeaderText("Unsaved profile"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[SELECT] discard canceled"); } return; } } if (logger.isDebugEnabled()) { logger.debug("[SELECT] nv={}", new_v); } doLoadProfile(new_v); }); lvProfiles.setCellFactory(TextFieldListCell.forListView()); Task<Void> t = new Task<Void>() { @Override protected Void call() throws Exception { updateMessage("Loading configuration"); configurationDS.loadConfiguration(); if (!configurationDS.isSecured()) { if (logger.isDebugEnabled()) { logger.debug("[CALL] config not secured; getting password"); } NewPasswordController npc = newPasswordControllerProvider.get(); if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] npc id={}", npc.hashCode()); } Platform.runLater(() -> { try { npc.showAndWait(); } catch (Exception exc) { logger.error("error showing npc", exc); } }); synchronized (npc) { try { npc.wait(MAX_WAIT_TIME); // 10 minutes to enter the password } catch (InterruptedException exc) { logger.error("new password operation interrupted", exc); } } if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] npc={}", npc.getHashedPassword()); } if (StringUtils.isNotEmpty(npc.getHashedPassword())) { activeConfiguration.setHashedPassword(npc.getHashedPassword()); activeConfiguration.setUnhashedPassword(npc.getUnhashedPassword()); activeConfiguration.setLastUpdatedDateTime(LocalDateTime.now()); configurationDS.saveConfiguration(); configurationDS.loadConfiguration(); configurationDS.decrypt(activeConfiguration.getUnhashedPassword()); } else { Platform.runLater(() -> { Alert noPassword = new Alert(Alert.AlertType.INFORMATION, "You'll need to provide a password to save your keystore credentials."); noPassword.showAndWait(); }); return null; } } else { PasswordController pc = passwordControllerProvider.get(); Platform.runLater(() -> { try { pc.showAndWait(); } catch (Exception exc) { logger.error("error showing pc", exc); } }); synchronized (pc) { try { pc.wait(MAX_WAIT_TIME); // 10 minutes to enter the password } catch (InterruptedException exc) { logger.error("password operation interrupted", exc); } } Platform.runLater(() -> { if (pc.getStage().isShowing()) { // ended in timeout timeout pc.getStage().hide(); } if (pc.wasCancelled() || pc.wasReset() || !pc.doesPasswordMatch()) { if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] was cancelled or the number of retries was exceeded"); } String msg = ""; if (pc.wasCancelled()) { msg = "You must provide a password to the datastore. Exitting..."; } else if (pc.wasReset()) { msg = "Data file removed. Exitting..."; } else { msg = "Exceeded maximum number of retries. Exitting..."; } Alert alert = new Alert(Alert.AlertType.WARNING, msg); alert.setOnCloseRequest((evt) -> { Platform.exit(); System.exit(1); }); alert.showAndWait(); } else { // // save password for later decryption ops // activeConfiguration.setUnhashedPassword(pc.getPassword()); configurationDS.decrypt(activeConfiguration.getUnhashedPassword()); // // init profileBrowser // if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] loading profiles from source"); } long startTimeMillis = System.currentTimeMillis(); final List<String> profileNames = configurationDS.getProfiles().stream() .map(Profile::getProfileName).sorted((o1, o2) -> o1.compareToIgnoreCase(o2)) .collect(Collectors.toList()); final List<String> recentProfiles = configurationDS.getRecentProfileNames(); if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] loading profiles into UI"); } lvProfiles.setItems(FXCollections.observableArrayList(profileNames)); if (CollectionUtils.isNotEmpty(recentProfiles)) { mRecentProfiles.getItems().clear(); mRecentProfiles.getItems().addAll( FXCollections.observableArrayList(recentProfiles.stream().map((s) -> { MenuItem mi = new MenuItem(s); mi.setOnAction(recentProfileLoadHandler); return mi; }).collect(Collectors.toList()))); } // // #31 preload the last active profile // if (StringUtils.isNotEmpty(activeConfiguration.getActiveProfile())) { if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] preloading last active profile={}", activeConfiguration.getActiveProfile()); } doLoadProfile(activeConfiguration.getActiveProfile()); } long endTimeMillis = System.currentTimeMillis(); if (logger.isDebugEnabled()) { logger.debug("[INIT TASK] loading profiles took {} ms", (endTimeMillis - startTimeMillis)); } } }); } return null; } @Override protected void succeeded() { super.succeeded(); updateMessage(""); lblStatus.textProperty().unbind(); } @Override protected void cancelled() { super.cancelled(); logger.error("task cancelled", getException()); updateMessage(""); lblStatus.textProperty().unbind(); } @Override protected void failed() { super.failed(); logger.error("task failed", getException()); updateMessage(""); lblStatus.textProperty().unbind(); } }; lblStatus.textProperty().bind(t.messageProperty()); new Thread(t).start(); } catch (Exception exc) { logger.error("can't load configuration", exc); String msg = "Verify that the user has access to the directory '" + configFile + "' under " + System.getProperty("user.home") + "."; Alert alert = new Alert(Alert.AlertType.ERROR, msg); alert.setHeaderText("Can't load config file"); alert.showAndWait(); Platform.exit(); } } @FXML public void showConsole(ActionEvent evt) { CheckMenuItem mi = (CheckMenuItem) evt.getSource(); if (logger.isDebugEnabled()) { logger.debug("[SHOW] show={}", mi.isSelected()); } if (mi.isSelected() && !sp.getItems().contains(console)) { if (logger.isDebugEnabled()) { logger.debug("[SHOW] adding console region"); } console.setOpacity(0.0d); sp.getItems().add(console); FadeTransition ft = new FadeTransition(Duration.millis(400), console); ft.setFromValue(0.0); ft.setToValue(1.0); ft.setCycleCount(1); ft.setAutoReverse(false); ft.play(); return; } if (!mi.isSelected() && sp.getItems().contains(console)) { if (logger.isDebugEnabled()) { logger.debug("[SHOW] removing console region"); } FadeTransition ft = new FadeTransition(Duration.millis(300), console); ft.setFromValue(1.0); ft.setToValue(0.1); ft.setCycleCount(1); ft.setAutoReverse(false); ft.play(); ft.setOnFinished((e) -> sp.getItems().remove(console)); } } @FXML public void showProfileBrowser(ActionEvent evt) { CheckMenuItem mi = (CheckMenuItem) evt.getSource(); if (logger.isDebugEnabled()) { logger.debug("[SHOW] show={}", mi.isSelected()); } if (mi.isSelected() && !outerSp.getItems().contains(profileBrowser)) { if (logger.isDebugEnabled()) { logger.debug("[SHOW] adding profileBrowser region"); } profileBrowser.setOpacity(0.0d); outerSp.getItems().add(0, profileBrowser); outerSp.setDividerPositions(0.3); FadeTransition ft = new FadeTransition(Duration.millis(400), profileBrowser); ft.setFromValue(0.0); ft.setToValue(1.0); ft.setCycleCount(1); ft.setAutoReverse(false); ft.play(); return; } if (!mi.isSelected() && outerSp.getItems().contains(profileBrowser)) { if (logger.isDebugEnabled()) { logger.debug("[SHOW] removing profileBrowser region"); } FadeTransition ft = new FadeTransition(Duration.millis(300), profileBrowser); ft.setFromValue(1.0); ft.setToValue(0.1); ft.setCycleCount(1); ft.setAutoReverse(false); ft.play(); ft.setOnFinished((e) -> outerSp.getItems().remove(profileBrowser)); } } @FXML public void close(ActionEvent evt) { doClose(); } @FXML public void newProfile() { if (logger.isDebugEnabled()) { logger.debug("[LOAD PROFILE]"); } activeProfile.reset(); Stage s = (Stage) sp.getScene().getWindow(); s.setTitle("ResignatorApp"); } @FXML public void showAbout() { if (logger.isDebugEnabled()) { logger.debug("[SHOW ABOUT]"); } AboutController about = aboutControllerProvider.get(); // singleton try { about.showAndWait(); } catch (Exception exc) { logger.error("error showing about screen", exc); } } @FXML public void loadProfile() { if (logger.isDebugEnabled()) { logger.debug("[LOAD PROFILE]"); } // // Clear output from last operation // Preconditions.checkArgument(!lblStatus.textProperty().isBound()); lblStatus.setText(""); txtConsole.setText(""); piSignProgress.setProgress(0.0d); piSignProgress.setVisible(false); clearValidationErrors(); // // Get profiles from loaded Configuration object // List<Profile> profiles = configurationDS.getProfiles(); if (CollectionUtils.isEmpty(profiles)) { if (logger.isDebugEnabled()) { logger.debug("[LOAD PROFILE] no profiles"); } String msg = "Select File > Save Profile to save the active profile."; Alert alert = new Alert(Alert.AlertType.INFORMATION, msg); alert.setHeaderText("No profiles saved"); alert.showAndWait(); return; } // // Distill list of profile names from List of Profile objects // List<String> profileNames = profiles.stream().sorted(comparing(Profile::getProfileName)) .map(Profile::getProfileName).collect(toList()); // // Select default item which is active item if available otherwise first item // String defaultProfileName = profileNames.get(0); // // Prompt user for selection - default is first item // if (StringUtils.isNotEmpty(activeConfiguration.activeProfileProperty().getValue())) { defaultProfileName = activeConfiguration.activeProfileProperty().getValue(); } if (logger.isDebugEnabled()) { logger.debug("[LOAD PROFILE] default profileName={}", defaultProfileName); } Dialog<String> dialog = new ChoiceDialog<>(defaultProfileName, profileNames); dialog.setTitle("Profile"); dialog.setHeaderText("Select profile "); Optional<String> result = dialog.showAndWait(); if (!result.isPresent()) { return; // cancel } if (logger.isDebugEnabled()) { logger.debug("[LOAD PROFILE] selected={}", result.get()); } doLoadProfile(result.get()); } private void doLoadProfile(String profileName) { configurationDS.loadProfile(profileName); Stage s = (Stage) sp.getScene().getWindow(); s.setTitle("ResignatorApp - " + profileName); needsSave.set(false); // this was just loaded // // #25 set in project browser // if (outerSp.getItems().contains(profileBrowser)) { // profile browser showing lvProfiles.getSelectionModel().select(profileName); } } @FXML public void saveProfile() { if (logger.isDebugEnabled()) { logger.debug("[SAVE PROFILE]"); } if (activeConfiguration.activeProfileProperty().isEmpty().get()) { if (logger.isDebugEnabled()) { logger.debug("[SAVE PROFILE] activeProfileName is empty"); } Dialog<String> dialog = new TextInputDialog(); dialog.setTitle("Profile name"); dialog.setHeaderText("Enter profile name"); Optional<String> result = dialog.showAndWait(); if (result.isPresent()) { String newProfileName = result.get(); if (configurationDS.profileExists(newProfileName)) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Overwrite existing profile?"); alert.setHeaderText("Profile exists"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { return; } } activeConfiguration.activeProfileProperty().set(newProfileName); try { recordRecentProfile(newProfileName); // #18 configurationDS.saveProfile(); // saves active profile Stage s = (Stage) sp.getScene().getWindow(); s.setTitle("ResignatorApp - " + newProfileName); needsSave.set(false); addToProfileBrowser(newProfileName); } catch (IOException exc) { logger.error("error saving profile '" + newProfileName + "'", exc); Alert alert = new Alert(Alert.AlertType.ERROR, exc.getMessage()); alert.setHeaderText("Can't save profile"); alert.showAndWait(); } } else { String msg = "A profile name is required"; Alert alert = new Alert(Alert.AlertType.ERROR, msg); alert.setHeaderText("Can't save profile"); alert.showAndWait(); } } else { // just save if (logger.isDebugEnabled()) { logger.debug("[SAVE PROFILE] there is an active profile"); } try { recordRecentProfile(activeProfile.getProfileName()); // #18 configurationDS.saveProfile(); // saves active profile needsSave.set(false); } catch (IOException exc) { logger.error("error saving profile '" + activeConfiguration.activeProfileProperty().get() + "'", exc); Alert alert = new Alert(Alert.AlertType.ERROR, exc.getMessage()); alert.setHeaderText("Can't save profile"); alert.showAndWait(); } } } private void addToProfileBrowser(String newProfileName) { if (!lvProfiles.getItems().contains(newProfileName)) { int pos = 0; for (; pos < CollectionUtils.size(lvProfiles.getItems()); pos++) { String pn = lvProfiles.getItems().get(pos); if (pn.compareToIgnoreCase(newProfileName) > 0) { break; } } lvProfiles.getItems().add(pos, newProfileName); lvProfiles.getSelectionModel().select(pos); } } void recordRecentProfile(String newProfileName) { if (activeConfiguration.getRecentProfiles().contains(newProfileName)) { return; // can assume activeConfiguration is accurate } // // #18 record recent history on save // List<MenuItem> rpItems = mRecentProfiles.getItems(); MenuItem rpItem = new MenuItem(newProfileName); rpItem.setOnAction(recentProfileLoadHandler); if (CollectionUtils.isNotEmpty(rpItems)) { if (CollectionUtils.size(rpItems) == 1) { if (StringUtils.equalsIgnoreCase(rpItems.get(0).getText(), MI_NO_PROFILES.getText())) { rpItems.set(0, rpItem); } else { rpItems.add(0, rpItem); } } else { rpItems.add(0, rpItem); if (CollectionUtils.size(rpItems) > numRecentProfiles) { for (int i = (CollectionUtils.size(rpItems) - 1); i >= numRecentProfiles; i--) { rpItems.remove(i); } } } } else { // should never have no items (at least one < None >) rpItems.add(rpItem); } // reconcile with active record activeConfiguration.getRecentProfiles().clear(); if (!(CollectionUtils.size(rpItems) == 1 && StringUtils.equalsIgnoreCase(rpItems.get(0).getText(), MI_NO_PROFILES.getText()))) { // there's more than just a < None > element activeConfiguration .setRecentProfiles(rpItems.stream().map((mi) -> mi.getText()).collect(Collectors.toList())); } // end #18 } @FXML public void saveAsProfile() { Dialog<String> dialog = new TextInputDialog(); dialog.setTitle("Profile name"); dialog.setHeaderText("Enter profile name"); Optional<String> result = dialog.showAndWait(); if (result.isPresent()) { // // Check for uniqueness; prompt for overwrite // final String profileName = result.get(); if (profileNameInUse(profileName)) { if (logger.isDebugEnabled()) { logger.debug("[SAVE AS] profile name in use; prompt for overwrite"); } Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Overwrite existing profile '" + profileName + "'?"); alert.setHeaderText("Profile name in use"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[SAVE AS] overwrite canceled"); } return; } } if (configurationDS.profileExists(profileName)) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Overwrite existing profile?"); alert.setHeaderText("Profile exists"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { return; } } activeConfiguration.activeProfileProperty().set(profileName); // activeProfile object tweaked w. new name try { recordRecentProfile(activeProfile.getProfileName()); // #18 configurationDS.saveProfile(); Stage s = (Stage) sp.getScene().getWindow(); s.setTitle("ResignatorApp - " + profileName); needsSave.set(false); addToProfileBrowser(profileName); } catch (IOException exc) { logger.error("error saving profile '" + profileName + "'", exc); Alert alert = new Alert(Alert.AlertType.ERROR, exc.getMessage()); alert.setHeaderText("Can't save profile"); alert.showAndWait(); } } else { String msg = "A profile name is required"; Alert alert = new Alert(Alert.AlertType.ERROR, msg); alert.setHeaderText("Can't save profile"); alert.showAndWait(); } } boolean profileNameInUse(String profileName) { return configurationDS.getProfiles().stream() .filter(p -> StringUtils.equalsIgnoreCase(p.getProfileName(), profileName)).count() > 0; } @FXML public void browseSource() { if (logger.isDebugEnabled()) { logger.debug("[BROWSE SOURCE]"); } clearValidationErrors(); switch (activeProfile.getArgsType()) { case JAR: FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select Source JAR"); fileChooser.setInitialDirectory(new File(jarDir)); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("JAR", "*.jar")); File f = fileChooser.showOpenDialog(stage); if (f != null) { if (logger.isDebugEnabled()) { logger.debug("[BROWSE SOURCE] selected file={}", f.getAbsolutePath()); } tfSourceFile.setText(f.getAbsolutePath()); jarDir = FilenameUtils.getFullPath(f.getAbsolutePath()); } break; case FOLDER: DirectoryChooser dirChooser = new DirectoryChooser(); dirChooser.setTitle("Select Source Folder"); dirChooser.setInitialDirectory(new File(jarDir)); File d = dirChooser.showDialog(stage); if (d != null) { if (logger.isDebugEnabled()) { logger.debug("[BROWSE SOURCE] selected dir={}", d.getAbsolutePath()); } tfSourceFile.setText(d.getAbsolutePath()); jarDir = FilenameUtils.getFullPath(d.getAbsolutePath()); } break; } } @FXML public void browseTarget() { if (logger.isDebugEnabled()) { logger.debug("[BROWSE TARGET]"); } clearValidationErrors(); switch (activeProfile.getArgsType()) { case JAR: FileChooser fileChooser = new FileChooser(); fileChooser.setTitle("Select Target JAR"); fileChooser.setInitialDirectory(new File(jarDir)); fileChooser.getExtensionFilters().addAll(new FileChooser.ExtensionFilter("JAR", "*.jar")); File f = fileChooser.showOpenDialog(stage); if (f != null) { if (logger.isDebugEnabled()) { logger.debug("[BROWSE TARGET] selected file={}", f.getAbsolutePath()); } tfTargetFile.setText(f.getAbsolutePath()); jarDir = FilenameUtils.getFullPath(f.getAbsolutePath()); } break; case FOLDER: DirectoryChooser dirChooser = new DirectoryChooser(); dirChooser.setTitle("Select Target Folder"); dirChooser.setInitialDirectory(new File(jarDir)); File d = dirChooser.showDialog(stage); if (d != null) { if (logger.isDebugEnabled()) { logger.debug("[BROWSE TARGET] selected dir={}", d.getAbsolutePath()); } tfTargetFile.setText(d.getAbsolutePath()); jarDir = FilenameUtils.getFullPath(d.getAbsolutePath()); } break; } } @FXML public void copySourceToTarget() { if (logger.isDebugEnabled()) { logger.debug("[COPY SOURCE TO TARGET]"); } clearValidationErrors(); tfTargetFile.setText(tfSourceFile.getText()); } @FXML public void clearValidationErrors() { if (tfSourceFile.getStyleClass().contains("tf-validation-error")) { tfSourceFile.getStyleClass().remove("tf-validation-error"); } if (tfTargetFile.getStyleClass().contains("tf-validation-error")) { tfTargetFile.getStyleClass().remove("tf-validation-error"); } } private boolean validateSign() { if (logger.isDebugEnabled()) { logger.debug("[VALIDATE]"); } boolean isValid = true; // // Validate the Source JAR field // if (StringUtils.isBlank(activeProfile.getSourceFileFileName())) { if (!tfSourceFile.getStyleClass().contains("tf-validation-error")) { tfSourceFile.getStyleClass().add("tf-validation-error"); } isValid = false; } else { if (!new File(activeProfile.getSourceFileFileName()).exists()) { if (!tfSourceFile.getStyleClass().contains("tf-validation-error")) { tfSourceFile.getStyleClass().add("tf-validation-error"); } Alert alert = new Alert(Alert.AlertType.ERROR, "Specified Source " + activeProfile.getArgsType() + " does not exist"); alert.showAndWait(); isValid = false; } } // // Validate the TargetJAR field // if (StringUtils.isBlank(activeProfile.getTargetFileFileName())) { if (!tfTargetFile.getStyleClass().contains("tf-validation-error")) { tfTargetFile.getStyleClass().add("tf-validation-error"); } isValid = false; } if (activeProfile.getArgsType() == SigningArgumentsType.FOLDER) { if (StringUtils.equalsIgnoreCase(activeProfile.getSourceFileFileName(), activeProfile.getTargetFileFileName())) { if (!tfTargetFile.getStyleClass().contains("tf-validation-error")) { tfTargetFile.getStyleClass().add("tf-validation-error"); } Alert alert = new Alert(Alert.AlertType.ERROR, "Source folder and target folder cannot be the same"); alert.showAndWait(); isValid = false; } } // // #13 Validate the Jarsigner Config form // String jarsignerConfigField = ""; String jarsignerConfigMessage = ""; if (isValid && StringUtils.isBlank(activeProfile.getJarsignerConfigKeystore())) { jarsignerConfigField = "Keystore"; jarsignerConfigMessage = "A keystore must be specified"; } else if (isValid && StringUtils.isBlank(activeProfile.getJarsignerConfigStorepass())) { jarsignerConfigField = "Storepass"; jarsignerConfigMessage = "A password for the keystore must be specified"; } else if (isValid && StringUtils.isBlank(activeProfile.getJarsignerConfigAlias())) { jarsignerConfigField = "Alias"; jarsignerConfigMessage = "An alias for the key must be specified"; } else if (isValid && StringUtils.isBlank(activeProfile.getJarsignerConfigKeypass())) { jarsignerConfigField = "Keypass"; jarsignerConfigMessage = "A password for the key must be specified"; } if (StringUtils.isNotEmpty(jarsignerConfigMessage)) { if (logger.isDebugEnabled()) { logger.debug("[VALIDATE] jarsigner config not valid {}", jarsignerConfigMessage); } Alert alert = new Alert(Alert.AlertType.ERROR, "Set " + jarsignerConfigField + " in Configure"); alert.setHeaderText(jarsignerConfigMessage); FlowPane fp = new FlowPane(); Label lbl = new Label("Set " + jarsignerConfigField + " in "); Hyperlink link = new Hyperlink("Configure"); fp.getChildren().addAll(lbl, link); link.setOnAction((evt) -> { alert.close(); openJarsignerConfig(); }); alert.getDialogPane().contentProperty().set(fp); alert.showAndWait(); isValid = false; } // // #38 check keystore prior to running // KeytoolCommand keytoolCommand = keytoolCommandProvider.get(); Task<Boolean> keytoolTask = new Task<Boolean>() { @Override protected Boolean call() throws Exception { final List<String> aliases = keytoolCommand.findAliases( activeConfiguration.getKeytoolCommand().toString(), activeProfile.getJarsignerConfigKeystore(), activeProfile.getJarsignerConfigStorepass()); if (logger.isDebugEnabled()) { logger.debug("[KEYTOOL] # aliases=" + CollectionUtils.size(aliases)); } return true; } }; new Thread(keytoolTask).start(); try { if (!keytoolTask.get()) { if (logger.isDebugEnabled()) { logger.debug("[KEYTOOL] keystore or configuration not valid"); } isValid = false; } } catch (InterruptedException | ExecutionException e) { isValid = false; logger.error("error accessing keystore", e); Alert alert = new Alert(Alert.AlertType.ERROR, e.getMessage() // contains formatted string ); alert.showAndWait(); } return isValid; } private boolean confirmReplaceExisting() { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Overwrite any existing signatures in JAR?"); alert.setHeaderText("Overwrite signatures"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] overwrite cancelled"); } return false; } return true; } private boolean confirmOverwrite(File[] sourceJars) { List<File> existingFiles = new ArrayList<>(); for (File sf : sourceJars) { File tf = new File(activeProfile.getTargetFileFileName(), sf.getName()); if (tf.exists()) { existingFiles.add(tf); } } if (CollectionUtils.isNotEmpty(existingFiles)) { String msg = "Overwrite these files in '" + activeProfile.getTargetFileFileName() + "'?"; msg += System.getProperty("line.separator"); for (File f : existingFiles) { msg += System.getProperty("line.separator") + f.getName(); } Alert alert = new Alert(Alert.AlertType.CONFIRMATION, msg); Label msgLabel = new Label(msg); alert.setHeaderText("Overwrite existing files"); alert.getDialogPane().setContent(msgLabel); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] overwrite files cancelled"); } return false; } } return true; } @FXML public void sign() { if (logger.isDebugEnabled()) { logger.debug("[SIGN] activeProfile sourceFile={}, targetFile={}", activeProfile.getSourceFileFileName(), activeProfile.getTargetFileFileName()); } boolean isValid = validateSign(); if (!isValid) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] form not valid; returning"); } return; } final Boolean doUnsign = ckReplace.isSelected(); UnsignCommand unsignCommand = unsignCommandProvider.get(); SignCommand signCommand = signCommandProvider.get(); if (activeProfile.getArgsType() == SigningArgumentsType.FOLDER) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] signing folder full of jars"); } // // Get list of source JARs // File[] sourceJars = new File(activeProfile.getSourceFileFileName()) .listFiles((d, n) -> StringUtils.endsWithIgnoreCase(n, ".jar")); // // Report if no jars to sign and exit // if (sourceJars == null || sourceJars.length == 0) { Alert alert = new Alert(Alert.AlertType.INFORMATION, "There aren't any JARs to sign in '" + activeProfile.getTargetFileFileName() + "'"); alert.setHeaderText("No JARs to Sign"); alert.showAndWait(); return; } if (logger.isDebugEnabled()) { for (File f : sourceJars) { logger.debug("[SIGN] source jar={}, filename={}", f.getAbsolutePath(), f.getName()); } } // // Confirm replace operation // if (doUnsign && !confirmReplaceExisting()) { return; } // // Confirm overwriting of files // if (!confirmOverwrite(sourceJars)) { return; } // // This number is applied to the progress bar to report a particular // iterations unit-of-work (2 operations per jar) // double unitFactor = 1.0d / (sourceJars.length * 2.0d); Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { double accruedProgress = 0.0d; for (File sf : sourceJars) { File tf = new File(activeProfile.getTargetFileFileName(), sf.getName()); if (logger.isDebugEnabled()) { logger.debug("[SIGN] progress={}", accruedProgress); } updateMessage(""); Platform.runLater(() -> piSignProgress.setVisible(true)); updateProgress(accruedProgress, 1.0d); accruedProgress += unitFactor; if (doUnsign) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] doing bulk unsign operation"); } updateTitle("Unsigning JAR"); unsignCommand.unsignJAR(Paths.get(sf.getAbsolutePath()), Paths.get(tf.getAbsolutePath()), s -> Platform.runLater( () -> txtConsole.appendText(s + System.getProperty("line.separator")))); if (isCancelled()) { return null; } } else { if (logger.isDebugEnabled()) { logger.debug("[SIGN] copying bulk for sign operation"); } updateTitle("Copying JAR"); Platform.runLater(() -> txtConsole .appendText("Copying JAR" + System.getProperty("line.separator"))); unsignCommand.copyJAR(sf.getAbsolutePath(), tf.getAbsolutePath()); } updateProgress(accruedProgress, 1.0d); accruedProgress += unitFactor; updateTitle("Signing JAR"); signCommand.signJAR(Paths.get(tf.getAbsolutePath()), Paths.get(activeProfile.getJarsignerConfigKeystore()), activeProfile.getJarsignerConfigStorepass(), activeProfile.getJarsignerConfigAlias(), activeProfile.getJarsignerConfigKeypass(), s -> Platform.runLater( () -> txtConsole.appendText(s + System.getProperty("line.separator")))); } return null; } @Override protected void succeeded() { super.succeeded(); updateProgress(1.0d, 1.0d); updateMessage("JARs signed successfully"); piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); } @Override protected void failed() { super.failed(); logger.error("error unsigning and signing jar", exceptionProperty().getValue()); updateProgress(1.0d, 1.0d); updateMessage("Error signing JARs"); piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); piSignProgress.setVisible(false); Alert alert = new Alert(Alert.AlertType.ERROR, exceptionProperty().getValue().getMessage()); alert.showAndWait(); } @Override protected void cancelled() { super.cancelled(); if (logger.isWarnEnabled()) { logger.warn("signing jar operation cancelled"); } updateProgress(1.0d, 1.0d); updateMessage("JARs signing cancelled"); Platform.runLater(() -> { piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); piSignProgress.setVisible(false); Alert alert = new Alert(Alert.AlertType.INFORMATION, "JARs signing cancelled"); alert.showAndWait(); }); } }; piSignProgress.progressProperty().bind(task.progressProperty()); lblStatus.textProperty().bind(task.messageProperty()); new Thread(task).start(); } else { if (logger.isDebugEnabled()) { logger.debug("[SIGN] signing single JAR"); } // // #2 confirm an overwrite (if needed); factored // if (doUnsign && !confirmReplaceExisting()) { return; } else { // // #6 sign-only to a different target filename needs a copy and // possible overwrite // File tf = new File(activeProfile.getTargetFileFileName()); if (tf.exists()) { Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Overwrite existing file '" + tf.getName() + "'?"); alert.setHeaderText("Overwrite existing file"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] overwrite file cancelled"); } return; } } } Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { updateMessage(""); Platform.runLater(() -> piSignProgress.setVisible(true)); updateProgress(0.1d, 1.0d); if (doUnsign) { if (logger.isDebugEnabled()) { logger.debug("[SIGN] doing unsign operation"); } updateTitle("Unsigning JAR"); unsignCommand.unsignJAR(Paths.get(activeProfile.getSourceFileFileName()), Paths.get(activeProfile.getTargetFileFileName()), s -> Platform.runLater( () -> txtConsole.appendText(s + System.getProperty("line.separator")))); if (isCancelled()) { return null; } } else { // // #6 needs a copy to the target if target file doesn't // exist // if (logger.isDebugEnabled()) { logger.debug("[SIGN] copying for sign operation"); } updateTitle("Copying JAR"); Platform.runLater( () -> txtConsole.appendText("Copying JAR" + System.getProperty("line.separator"))); unsignCommand.copyJAR(activeProfile.getSourceFileFileName(), activeProfile.getTargetFileFileName()); } updateProgress(0.5d, 1.0d); updateTitle("Signing JAR"); signCommand.signJAR(Paths.get(activeProfile.getTargetFileFileName()), Paths.get(activeProfile.getJarsignerConfigKeystore()), activeProfile.getJarsignerConfigStorepass(), activeProfile.getJarsignerConfigAlias(), activeProfile.getJarsignerConfigKeypass(), s -> Platform.runLater( () -> txtConsole.appendText(s + System.getProperty("line.separator")))); return null; } @Override protected void succeeded() { super.succeeded(); updateProgress(1.0d, 1.0d); updateMessage("JAR signed successfully"); piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); } @Override protected void failed() { super.failed(); logger.error("error unsigning and signing jar", exceptionProperty().getValue()); updateProgress(1.0d, 1.0d); updateMessage("Error signing JAR"); piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); piSignProgress.setVisible(false); Alert alert = new Alert(Alert.AlertType.ERROR, exceptionProperty().getValue().getMessage()); alert.showAndWait(); } @Override protected void cancelled() { super.cancelled(); if (logger.isWarnEnabled()) { logger.warn("signing jar operation cancelled"); } updateProgress(1.0d, 1.0d); updateMessage("JAR signing cancelled"); piSignProgress.progressProperty().unbind(); lblStatus.textProperty().unbind(); piSignProgress.setVisible(false); Alert alert = new Alert(Alert.AlertType.INFORMATION, "JAR signing cancelled"); alert.showAndWait(); } }; piSignProgress.progressProperty().bind(task.progressProperty()); lblStatus.textProperty().bind(task.messageProperty()); new Thread(task).start(); } } @FXML public void openSettings() { SettingsController settingsView = settingsControllerProvider.get(); try { settingsView.show(); // ignoring the primaryStage } catch (Exception exc) { String msg = "Error launching Settings"; logger.error(msg, exc); Alert alert = new Alert(Alert.AlertType.ERROR, msg); alert.showAndWait(); } } @FXML public void openJarsignerConfig() { clearValidationErrors(); if (StringUtils.isNotEmpty(activeConfiguration.getJDKHome())) { JarsignerConfigController jarsignerConfigView = jarsignerConfigControllerProvider.get(); jarsignerConfigView.setParent(this); try { jarsignerConfigView.show(); } catch (Exception exc) { String msg = "Error launching jarsigner config"; logger.error(msg, exc); Alert alert = new Alert(Alert.AlertType.ERROR, msg); alert.showAndWait(); } } else { if (logger.isDebugEnabled()) { logger.debug("[OPEN JARSIGNER CONFIG] JDK_HOME not set"); } Alert alert = new Alert(Alert.AlertType.ERROR, "Set JDK_HOME in File > Settings"); alert.setHeaderText("JDK_HOME not defined"); FlowPane fp = new FlowPane(); Label lbl = new Label("Set JDK_HOME in "); Hyperlink link = new Hyperlink("File > Settings"); fp.getChildren().addAll(lbl, link); link.setOnAction((evt) -> { alert.close(); openSettings(); }); alert.getDialogPane().contentProperty().set(fp); alert.showAndWait(); } } @FXML public void deleteProfile() { final String profileNameToDelete = lvProfiles.getSelectionModel().getSelectedItem(); if (logger.isDebugEnabled()) { logger.debug("[DELETE PROFILE] delete {}", profileNameToDelete); } Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Delete profile '" + profileNameToDelete + "'?"); alert.setHeaderText("Delete profile"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[DELETE PROFILE] delete profile cancelled"); } return; } final boolean apProfileNameSetFlag = StringUtils.equalsIgnoreCase(activeProfile.getProfileName(), profileNameToDelete); if (apProfileNameSetFlag) { activeProfile.setProfileName(""); } Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { // // #18 adjust record prior to dao call // if (activeConfiguration.getRecentProfiles().contains(profileNameToDelete)) { activeConfiguration.getRecentProfiles().remove(profileNameToDelete); } if (StringUtils.equalsIgnoreCase(profileNameToDelete, activeConfiguration.getActiveProfile())) { activeConfiguration.setActiveProfile(null); } configurationDS.deleteProfile(profileNameToDelete); return null; } @Override protected void succeeded() { super.succeeded(); Platform.runLater(() -> { // #18 recent profiles Iterator<MenuItem> iterator = mRecentProfiles.getItems().iterator(); while (iterator.hasNext()) { MenuItem mi = iterator.next(); if (StringUtils.equalsIgnoreCase(mi.getText(), profileNameToDelete)) { iterator.remove(); } } if (CollectionUtils.isEmpty(mRecentProfiles.getItems())) { mRecentProfiles.getItems().add(MI_NO_PROFILES); } lvProfiles.getItems().remove(profileNameToDelete); if (StringUtils.equalsIgnoreCase(profileNameToDelete, activeProfile.getProfileName())) { newProfile(); } needsSave.set(false); }); } @Override protected void failed() { super.failed(); Alert alert = new Alert(Alert.AlertType.ERROR, getException().getMessage()); alert.setHeaderText("Error deleting profile"); alert.showAndWait(); } }; new Thread(task).start(); } @Override protected void postInit() throws Exception { super.postInit(); stage.setMinHeight(600.0d); stage.setMinWidth(1280.0d); stage.setOnCloseRequest((evt) -> doClose()); } protected void doClose() { if (logger.isDebugEnabled()) { logger.debug("[DO CLOSE] shutting down"); } if (needsSave.get()) { if (logger.isDebugEnabled()) { logger.debug("[CLOSE] profile needs saving"); } ButtonType myCancel = new ButtonType("Discard Changes", ButtonBar.ButtonData.CANCEL_CLOSE); Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "Save profile?", ButtonType.OK, myCancel); alert.setHeaderText("Unsaved profile"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[CLOSE] skipping save"); } } else { if (logger.isDebugEnabled()) { logger.debug("[CLOSE] saving"); } saveProfile(); needsSave.set(false); // needed in case failure infinite cycle } } Platform.exit(); System.exit(0); // close webstart } @FXML public void handleProjectBrowserKey(KeyEvent evt) { if (logger.isDebugEnabled()) { logger.debug("[KEY] key={}", evt.getCode()); } if (StringUtils.isNotEmpty(lvProfiles.getSelectionModel().getSelectedItem())) { switch (evt.getCode()) { case DELETE: if (logger.isDebugEnabled()) { logger.debug("[KEY] deleting"); } deleteProfile(); break; case F2: int index = lvProfiles.getSelectionModel().getSelectedIndex(); if (logger.isDebugEnabled()) { logger.debug("[KEY] renaming index={}", index); } lvProfiles.edit(index); break; default: break; } //evt.consume(); } } @FXML public void renameProfile(ListView.EditEvent<String> evt) { if (logger.isDebugEnabled()) { logger.debug("[RENAME]"); } final String oldProfileName = lvProfiles.getItems().get(evt.getIndex()); final boolean apProfileNameSetFlag = StringUtils.equalsIgnoreCase(activeProfile.getProfileName(), oldProfileName); String suggestedNewProfileName = ""; if (configurationDS.profileExists(evt.getNewValue())) { suggestedNewProfileName = configurationDS.suggestUniqueProfileName(evt.getNewValue()); if (logger.isDebugEnabled()) { logger.debug("[RENAME] configuration exists; suggested name={}", suggestedNewProfileName); } Alert alert = new Alert(Alert.AlertType.CONFIRMATION, "That profile name already exists." + System.getProperty("line.separator") + "Save as " + suggestedNewProfileName + "?"); alert.setHeaderText("Profile name in use"); Optional<ButtonType> response = alert.showAndWait(); if (!response.isPresent() || response.get() != ButtonType.OK) { if (logger.isDebugEnabled()) { logger.debug("[RENAME] rename cancelled"); } return; } } final String newProfileName = StringUtils.defaultIfBlank(suggestedNewProfileName, evt.getNewValue()); if (apProfileNameSetFlag) { // needs to be set for save activeProfile.setProfileName(newProfileName); } Task<Void> renameTask = new Task<Void>() { @Override protected Void call() throws Exception { configurationDS.renameProfile(oldProfileName, newProfileName); Platform.runLater(() -> { lvProfiles.getItems().set(evt.getIndex(), newProfileName); Collections.sort(lvProfiles.getItems()); lvProfiles.getSelectionModel().select(newProfileName); }); return null; } @Override protected void failed() { super.failed(); logger.error("can't rename profile from " + oldProfileName + " to " + newProfileName, getException()); Alert alert = new Alert(Alert.AlertType.ERROR, getException().getMessage()); alert.setHeaderText("Can't rename profile '" + oldProfileName + "'"); alert.showAndWait(); if (apProfileNameSetFlag) { // revert activeProfile.setProfileName(oldProfileName); } } @Override protected void cancelled() { super.cancelled(); Alert alert = new Alert(Alert.AlertType.ERROR, "Rename cancelled by user"); alert.setHeaderText("Cancelled"); alert.showAndWait(); if (apProfileNameSetFlag) { // revert activeProfile.setProfileName(oldProfileName); } } }; new Thread(renameTask).start(); } @FXML public void rename() { int index = lvProfiles.getSelectionModel().getSelectedIndex(); if (logger.isDebugEnabled()) { logger.debug("[CONTEXT MENU] renaming index={}", index); } lvProfiles.edit(index); } @FXML public void changePassword() { Task<Void> task = new Task<Void>() { @Override protected Void call() throws Exception { NewPasswordController npc = newPasswordControllerProvider.get(); Platform.runLater(() -> { try { npc.showAndWait(); } catch (Exception exc) { logger.error("error showing change password form", exc); } }); synchronized (npc) { try { npc.wait(MAX_WAIT_TIME); // 10 minutes to enter the password } catch (InterruptedException exc) { logger.error("change password operation interrupted", exc); } } if (logger.isDebugEnabled()) { logger.debug("[CHANGE PASSWORD] npc={}", npc.getHashedPassword()); } if (StringUtils.isNotEmpty(npc.getHashedPassword())) { activeConfiguration.setHashedPassword(npc.getHashedPassword()); activeConfiguration.setUnhashedPassword(npc.getUnhashedPassword()); activeConfiguration.setLastUpdatedDateTime(LocalDateTime.now()); configurationDS.saveConfiguration(); configurationDS.loadConfiguration(); } else { Platform.runLater(() -> { Alert noPassword = new Alert(Alert.AlertType.INFORMATION, "Password change cancelled."); noPassword.showAndWait(); }); } return null; } }; new Thread(task).start(); } @FXML public void showHelp() { helpDelegate.showHelp(); } @FXML public void clearConsole() { txtConsole.clear(); piSignProgress.setVisible(false); } }