/* Copyright 1995-2015 Esri 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. For additional information, contact: Environmental Systems Research Institute, Inc. Attn: Contracts Dept 380 New York Street Redlands, California, USA 92373 email: contracts@esri.com */ package com.esri.geoevent.test.performance.ui; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.net.URL; import java.nio.file.Paths; import java.util.ArrayList; import java.util.ResourceBundle; import javafx.application.Platform; import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.fxml.FXMLLoader; import javafx.fxml.Initializable; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.Menu; import javafx.scene.control.MenuBar; import javafx.scene.control.MenuItem; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.control.Tooltip; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.InputEvent; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.layout.AnchorPane; import javafx.stage.FileChooser; import javafx.stage.Modality; import javafx.stage.Stage; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.stream.StreamSource; import org.apache.commons.lang3.StringUtils; import com.esri.geoevent.test.performance.Mode; import com.esri.geoevent.test.performance.OrchestratorRunner; import com.esri.geoevent.test.performance.RunningException; import com.esri.geoevent.test.performance.RunningState; import com.esri.geoevent.test.performance.RunningStateListener; import com.esri.geoevent.test.performance.jaxb.Fixture; import com.esri.geoevent.test.performance.jaxb.Fixtures; import com.esri.geoevent.test.performance.jaxb.Report; public class OrchestratorController implements Initializable, RunningStateListener { //UI Elements @FXML public Label titleLabel; // menus @FXML public MenuBar menuBar; @FXML public Menu fileMenu; @FXML public MenuItem fileOpenMenuItem; @FXML public MenuItem fileSaveMenuItem; @FXML public Menu optionsMenu; @FXML public MenuItem optionsReportMenuItem; @FXML public MenuItem optionsLoggerMenuItem; @FXML public Menu helpMenu; @FXML public MenuItem helpAboutMenuItem; // fixtures @FXML public TabPane fixtureTabPane; @FXML public Button addFixtureBtn; @FXML public Button startBtn; @FXML public Label statusLabel; // member vars private Fixtures fixtures = new Fixtures(); private Stage stage; private boolean isRunning; private OrchestratorRunner executor = null; private StringBuffer outputBuffer = new StringBuffer(); // statics private static final String START_IMAGE_SOURCE = "images/play.png"; private static final String STOP_IMAGE_SOURCE = "images/stop.png"; public static final String DEFAULT_FIXTURES_FILE = "fixtures.xml"; /** * Set up some defaults */ public OrchestratorController() { this.fixtures = new Fixtures(); this.fixtures.setDefaultFixture(new Fixture("Default")); this.fixtures.setReport(new Report()); this.fixtures.setFixtures(new ArrayList<Fixture>()); } /** * Sets the stage of this controller. * * @param stage dialogStage */ public void setStage(Stage stage) { this.stage = stage; } @Override public void initialize(URL location, ResourceBundle resources) { titleLabel.setText(Mode.Orchestrator.toString()); fileMenu.setText(UIMessages.getMessage("UI_FILE_MENU_LABEL")); fileOpenMenuItem.setText(UIMessages.getMessage("UI_FILE_OPEN_MENU_ITEM_LABEL")); fileSaveMenuItem.setText(UIMessages.getMessage("UI_FILE_SAVE_MENU_ITEM_LABEL")); optionsMenu.setText(UIMessages.getMessage("UI_OPTIONS_MENU_LABEL")); optionsLoggerMenuItem.setText(UIMessages.getMessage("UI_OPTIONS_LOGGER_MENU_ITEM_LABEL")); optionsReportMenuItem.setText(UIMessages.getMessage("UI_OPTIONS_REPORT_MENU_ITEM_LABEL")); helpMenu.setText(UIMessages.getMessage("UI_HELP_MENU_LABEL")); helpAboutMenuItem.setText(UIMessages.getMessage("UI_HELP_ABOUT_MENU_ITEM_LABEL")); addFixtureBtn.setTooltip(new Tooltip(UIMessages.getMessage("UI_ADD_FIXTURE_DESC"))); startBtn.setText(UIMessages.getMessage("UI_START_BTN_LABEL")); startBtn.setTooltip(new Tooltip(UIMessages.getMessage("UI_START_BTN_DESC"))); //TODO: We need use this to show status messages statusLabel.setText("This is where the status changes will be placed."); // setup setupTabs(); redirectSystemOutAndErrToTextArea(); } /** * Handle action related to input (in this case specifically only responds * to keyboard event CTRL-A). * * @param event Input event. */ @FXML public void handleKeyInput(final InputEvent event) { if (event instanceof KeyEvent) { final KeyEvent keyEvent = (KeyEvent) event; if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.A) { provideAboutFunctionality(); } else if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.R) { showReportOptionsDialog(); } else if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.O) { openFixturesFile(); } else if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.S) { saveFixturesFile(); } else if (keyEvent.isControlDown() && keyEvent.getCode() == KeyCode.L) { showLoggerDialog(); } } } /** * Handle action related to "About" menu item. * * @param event Event on "About" menu item. */ @FXML public void handleAboutAction(final ActionEvent event) { provideAboutFunctionality(); } /** * Opens a dialog to display the current output. * * @param event ActionEvent */ @FXML public void handleLoggerOptionsAction(final ActionEvent event) { showLoggerDialog(); } /** * Opens a dialog to edit the report options. If the user clicks OK, the * changes are saved into the main configuration. * * @param event ActionEvent */ @FXML public void handleReportOptionsAction(final ActionEvent event) { showReportOptionsDialog(); } /** * Opens a file chooser dialog to load a fixtures.xml configuration file. * * @param event {@link ActionEvent} */ @FXML public void handleOpenAction(final ActionEvent event) { openFixturesFile(); } /** * Saves the current configuration as an xml file. * * @param event {@link ActionEvent} */ @FXML public void handleSaveAction(final ActionEvent event) { saveFixturesFile(); } /** * Adds a new fixture to the TabPane and to the fixtures model * * @param event ActionEvent */ @FXML public void addFixture(final ActionEvent event) { Fixture defaultFixture = fixtures.getDefaultFixture(); Fixture newFixture = new Fixture("NewFixture" + (fixtures.getFixtures().size() + 1)); newFixture.apply(defaultFixture); fixtures.getFixtures().add(newFixture); addFixtureTab(newFixture, false); } /** * This is the main method which is used to start the simulation test * * @param event {@link ActionEvent} not used. */ @FXML public void startTest(final ActionEvent event) { toggleRunningState(!isRunning); } /** * Helper method to toggle the UI running state */ private void toggleRunningState(boolean newRunningState) { isRunning = newRunningState; if (isRunning) { // start the executor try { this.executor = new OrchestratorRunner(fixtures); this.executor.start(); this.executor.setRunningStateListener(this); } catch (RunningException error) { //TODO: error handling error.printStackTrace(); } // toggle the ui startBtn.setText(UIMessages.getMessage("UI_STOP_BTN_LABEL")); startBtn.setTooltip(new Tooltip(UIMessages.getMessage("UI_STOP_BTN_DESC"))); startBtn.setGraphic(new ImageView(new Image(FixtureController.class.getResourceAsStream(STOP_IMAGE_SOURCE)))); } else { // stop execution if (this.executor != null) { if (this.executor.isRunning()) { this.executor.stop(); } else { this.executor = null; } } startBtn.setText(UIMessages.getMessage("UI_START_BTN_LABEL")); startBtn.setTooltip(new Tooltip(UIMessages.getMessage("UI_START_BTN_DESC"))); startBtn.setGraphic(new ImageView(new Image(FixtureController.class.getResourceAsStream(START_IMAGE_SOURCE)))); } } /** * Displays a file chooser to select a file to open */ private void openFixturesFile() { File currentDir = Paths.get("").toAbsolutePath().toFile(); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(UIMessages.getMessage("UI_OPEN_FILE_CHOOSER_TITLE")); fileChooser.setInitialDirectory(currentDir); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); fileChooser.setInitialFileName(DEFAULT_FIXTURES_FILE); File file = fileChooser.showOpenDialog(stage); if (file != null) { try { loadFile(file); setupTabs(); } catch (Exception error) { //TODO: Improve error handling and reporting error.printStackTrace(); } } } /** * Helper method to load a file and unmarshall it using JAXB * * @param file File to load * @throws JAXBException if there is a unmarshalling problem */ private void loadFile(File file) throws JAXBException { JAXBContext jaxbContext = JAXBContext.newInstance(Fixtures.class); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); StreamSource xml = new StreamSource(file.getAbsolutePath()); this.fixtures = (Fixtures) unmarshaller.unmarshal(xml); } /** * Displays a file chooser to select a file location to save the * configuration file. */ private void saveFixturesFile() { File currentDir = Paths.get("").toAbsolutePath().toFile(); FileChooser fileChooser = new FileChooser(); fileChooser.setTitle(UIMessages.getMessage("UI_SAVE_FILE_CHOOSER_TITLE")); fileChooser.setInitialDirectory(currentDir); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("XML", "*.xml")); fileChooser.setInitialFileName(DEFAULT_FIXTURES_FILE); File file = fileChooser.showSaveDialog(stage); if (file != null) { try { saveFile(file); } catch (Exception error) { //TODO: Improve error handling and reporting error.printStackTrace(); } } } /** * Saves the current fixtures file to disk. * * @param file File where to save the configuration file. * @throws JAXBException * @throws XMLStreamException * @throws IOException */ private void saveFile(File file) throws JAXBException, XMLStreamException, IOException { JAXBContext jaxbContext = JAXBContext.newInstance(Fixtures.class); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); FileWriter fileWriter = new FileWriter(file); XMLOutputFactory factory = XMLOutputFactory.newInstance(); XMLStreamWriter xmlWritter = factory.createXMLStreamWriter(fileWriter); marshaller.marshal(fixtures, xmlWritter); fileWriter.flush(); fileWriter.close(); } /** * Shows the report options dialog */ private void showReportOptionsDialog() { try { // Load the fxml file and create a new stage for the popup FXMLLoader loader = new FXMLLoader(getClass().getResource("ReportOptions.fxml")); AnchorPane page = (AnchorPane) loader.load(); Stage dialogStage = new Stage(); dialogStage.setTitle(UIMessages.getMessage("UI_REPORT_OPTIONS_TITLE")); dialogStage.initModality(Modality.APPLICATION_MODAL); dialogStage.initOwner(stage); Scene scene = new Scene(page); dialogStage.setScene(scene); // Set the person into the controller ReportOptionsController controller = loader.getController(); controller.setDialogStage(dialogStage); controller.setReport(this.fixtures.getReport()); //TODO: we need to clone our Report Object Here // Show the dialog and wait until the user closes it dialogStage.showAndWait(); if (controller.isOkClicked()) { this.fixtures.setReport(controller.getReport()); } } catch (IOException e) { e.printStackTrace(); } } /** * Shows the confirmation dialog */ private boolean showConfirmationDialog(String msg) { try { // Load the fxml file and create a new stage for the popup FXMLLoader loader = new FXMLLoader(getClass().getResource("ConfirmationDialog.fxml")); Parent page = (Parent) loader.load(); Stage dialogStage = new Stage(); dialogStage.setTitle(UIMessages.getMessage("UI_CLOSE_TAB_TITLE")); dialogStage.initModality(Modality.APPLICATION_MODAL); dialogStage.initOwner(stage); Scene scene = new Scene(page); dialogStage.setScene(scene); // Set the person into the controller ConfirmationDialogController controller = loader.getController(); controller.setDialogStage(dialogStage); controller.setConfirmationMsg(msg); // Show the dialog and wait until the user closes it dialogStage.showAndWait(); return controller.isOkClicked(); } catch (IOException e) { e.printStackTrace(); } return false; } /** * Perform functionality associated with "About" menu selection or CTRL-A. */ private void provideAboutFunctionality() { try { // Load the fxml file and create a new stage for the popup FXMLLoader loader = new FXMLLoader(getClass().getResource("AboutDialog.fxml")); Parent page = (Parent) loader.load(); Stage dialogStage = new Stage(); dialogStage.setTitle(UIMessages.getMessage("UI_HELP_ABOUT_MENU_ITEM_LABEL")); dialogStage.initModality(Modality.APPLICATION_MODAL); dialogStage.initOwner(stage); Scene scene = new Scene(page); dialogStage.setScene(scene); // Set the person into the controller AboutDialogController controller = loader.getController(); controller.setDialogStage(dialogStage); // Show the dialog and wait until the user closes it dialogStage.showAndWait(); } catch (IOException e) { e.printStackTrace(); } } /** * Perform functionality associated with "About" menu selection or CTRL-A. */ private void showLoggerDialog() { try { // Load the fxml file and create a new stage for the popup FXMLLoader loader = new FXMLLoader(getClass().getResource("LoggerDialog.fxml")); Parent page = (Parent) loader.load(); Stage dialogStage = new Stage(); dialogStage.setTitle(UIMessages.getMessage("UI_LOGGER_BOX_LABEL")); dialogStage.initModality(Modality.NONE); dialogStage.initOwner(stage); Scene scene = new Scene(page); dialogStage.setScene(scene); // Set the person into the controller LoggerDialogController controller = loader.getController(); controller.setDialogStage(dialogStage); controller.setOutputBuffer(outputBuffer.toString()); controller.onClearLoggerEvent(() -> outputBuffer = new StringBuffer()); // Show the dialog dialogStage.show(); } catch (IOException e) { e.printStackTrace(); } } private void setupTabs() { //clear them all out fixtureTabPane.getTabs().clear(); // setup the tabs if (fixtures.getDefaultFixture() != null) { if (StringUtils.isEmpty(fixtures.getDefaultFixture().getName())) { fixtures.getDefaultFixture().setName("Default"); } addFixtureTab(fixtures.getDefaultFixture(), true); } if (fixtures.getFixtures() != null) { for (Fixture fixture : fixtures.getFixtures()) { fixture.apply(fixtures.getDefaultFixture()); addFixtureTab(fixture, false); } } } private void addFixtureTab(final Fixture fixture, boolean isDefault) { try { FXMLLoader loader = new FXMLLoader(getClass().getResource("Fixture.fxml")); Parent fixtureTab = (Parent) loader.load(); FixtureController controller = loader.getController(); controller.setFixture(fixture); controller.setIsDefault(isDefault); Tab newTab = new Tab(fixture.getName()); newTab.setContent(fixtureTab); newTab.closableProperty().setValue(!isDefault); newTab.setOnCloseRequest(event -> { boolean isOkClicked = showConfirmationDialog(UIMessages.getMessage("UI_CLOSE_TAB_LABEL", fixture.getName())); if (!isOkClicked) { event.consume(); } else { fixtures.getFixtures().remove(fixture); } }); fixtureTabPane.getTabs().add(newTab); } catch (IOException e) { e.printStackTrace(); } } /** * Running State Listener - this is used so we get notified when the * orchestration is complete */ @Override public void onStateChange(RunningState newState) { switch (newState.getType()) { case STOPPED: toggleRunningState(false); break; default: break; } } private void redirectSystemOutAndErrToTextArea() { OutputStream out = new OutputStream() { @Override public void write(int b) throws IOException { Platform.runLater(() -> outputBuffer.append(String.valueOf((char) b))); } }; System.setOut(new PrintStream(out, true)); OutputStream err = new OutputStream() { @Override public void write(int b) throws IOException { Platform.runLater(() -> outputBuffer.append(String.valueOf((char) b))); } }; System.setErr(new PrintStream(err, true)); } }