Java tutorial
/** * ***************************************************************************** * Copyright (c) 2016 * * 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.exalttech.trex.ui.views; import com.exalttech.trex.remote.models.profiles.Profile; import com.exalttech.trex.ui.StreamBuilderType; import com.exalttech.trex.ui.components.CheckBoxTableViewCell; import com.exalttech.trex.ui.components.CheckBoxTableViewCell.CheckBoxTableChangeHandler; import com.exalttech.trex.ui.controllers.PacketBuilderHomeController; import com.exalttech.trex.ui.controllers.ProfileStreamNameDialogController; import com.exalttech.trex.ui.controllers.ImportPcapWizardController; import com.exalttech.trex.ui.dialog.DialogWindow; import com.exalttech.trex.ui.views.models.TableProfile; import com.exalttech.trex.ui.views.models.TableProfileStream; import com.exalttech.trex.ui.views.streamtable.StreamTableAction; import com.exalttech.trex.ui.views.streamtable.StreamTableButton; import com.exalttech.trex.util.TrafficProfile; import com.exalttech.trex.util.Util; import com.exalttech.trex.util.files.FileManager; import com.exalttech.trex.util.files.FileType; import com.fasterxml.jackson.core.JsonProcessingException; import com.sun.javafx.scene.control.skin.TableHeaderRow; import java.io.File; import java.io.IOException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; import javafx.collections.FXCollections; import javafx.event.ActionEvent; import javafx.event.EventHandler; import javafx.scene.control.Button; import javafx.scene.control.ContextMenu; import javafx.scene.control.MenuItem; import javafx.scene.control.SelectionMode; import javafx.scene.control.TableCell; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyCodeCombination; import javafx.scene.input.KeyCombination; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.HBox; import javafx.stage.Stage; import javafx.stage.Window; import javafx.util.Callback; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import org.pcap4j.core.NotOpenException; import org.pcap4j.core.PcapDumper; import org.pcap4j.core.PcapHandle; import org.pcap4j.core.PcapNativeException; import org.pcap4j.core.Pcaps; import org.pcap4j.packet.EthernetPacket; import org.pcap4j.packet.IllegalRawDataException; import org.pcap4j.packet.Packet; import org.pcap4j.packet.namednumber.DataLinkType; /** * Stream table view implementation * * @author Georgekh */ public class PacketTableView extends AnchorPane implements EventHandler<ActionEvent>, CheckBoxTableChangeHandler { private static final Logger LOG = Logger.getLogger(PacketTableView.class.getName()); StreamTableButton addPacketBtn; StreamTableButton buildPacketBtn; StreamTableButton editPacketBtn; StreamTableButton deleteButtonBtn; StreamTableButton exportPcapButton; StreamTableButton importPcapButton; StreamTableButton exportToYaml; TableView<TableProfileStream> streamPacketTableView; double maxHight; TableProfile tabledata; private boolean streamEditingWindowOpen; PacketTableUpdatedHandler tableUpdateHandler; TrafficProfile trafficProfile; final KeyCombination copyCombination = new KeyCodeCombination(KeyCode.C, KeyCombination.CONTROL_DOWN); final KeyCombination pasteCombination = new KeyCodeCombination(KeyCode.V, KeyCombination.CONTROL_DOWN); private Profile selectedProfile = null; private String profileName; int numOfStreamLoaded = 0; int numOfEnabledStream = 0; private boolean doUpdate = false; ContextMenu rightClickMenu; private File loadedProfile; /** * @param maxHight * @param tableUpdateHandler * @param addExportToYamlBtn */ public PacketTableView(double maxHight, PacketTableUpdatedHandler tableUpdateHandler, boolean addExportToYamlBtn) { this.maxHight = maxHight; this.tableUpdateHandler = tableUpdateHandler; trafficProfile = new TrafficProfile(); buildUI(addExportToYamlBtn); } /** * * @param maxHight */ public PacketTableView(double maxHight) { this(maxHight, null, false); } /** * Build component UI */ private void buildUI(boolean addExportToYamlBtn) { setTopAnchor(this, 0d); setLeftAnchor(this, 0d); setBottomAnchor(this, 0d); setRightAnchor(this, 0d); // build btn bar HBox buttonContainer = new HBox(); buttonContainer.setSpacing(5); addPacketBtn = new StreamTableButton(StreamTableAction.ADD); initializeStreamButtons(addPacketBtn, false); buttonContainer.getChildren().add(addPacketBtn); // add build stream btn buildPacketBtn = new StreamTableButton(StreamTableAction.BUILD); buildPacketBtn.setId("buildStreamBtn"); initializeStreamButtons(buildPacketBtn, false); buttonContainer.getChildren().add(buildPacketBtn); editPacketBtn = new StreamTableButton(StreamTableAction.EDIT); editPacketBtn.setId("editStreanBtn"); initializeStreamButtons(editPacketBtn, true); buttonContainer.getChildren().add(editPacketBtn); deleteButtonBtn = new StreamTableButton(StreamTableAction.DELETE); initializeStreamButtons(deleteButtonBtn, true); buttonContainer.getChildren().add(deleteButtonBtn); importPcapButton = new StreamTableButton(StreamTableAction.IMPORT_PCAP); initializeStreamButtons(importPcapButton, false); buttonContainer.getChildren().add(importPcapButton); exportPcapButton = new StreamTableButton(StreamTableAction.EXPORT_TO_PCAP); initializeStreamButtons(exportPcapButton, true); buttonContainer.getChildren().add(exportPcapButton); if (addExportToYamlBtn) { exportToYaml = new StreamTableButton(StreamTableAction.EXPORT_TO_YAML); initializeStreamButtons(exportToYaml, false); buttonContainer.getChildren().add(exportToYaml); } getChildren().add(buttonContainer); setTopAnchor(buttonContainer, 5d); rightClickMenu = new ContextMenu(); addMenuItem(StreamTableAction.EDIT); addMenuItem(StreamTableAction.DELETE); addMenuItem(StreamTableAction.EXPORT_TO_PCAP); addMenuItem(StreamTableAction.EXPORT_TO_YAML); // add table view streamPacketTableView = new TableView<>(); streamPacketTableView.setId("streamTableView"); streamPacketTableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); streamPacketTableView.setFixedCellSize(32); streamPacketTableView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); streamPacketTableView.setOnMouseClicked(new EventHandler<MouseEvent>() { @Override public void handle(MouseEvent event) { handleTableRowClick(event); } }); streamPacketTableView.getSelectionModel().selectedItemProperty() .addListener((ObservableValue<? extends TableProfileStream> observable, TableProfileStream oldValue, TableProfileStream newValue) -> { boolean notSelected = !(newValue != null); exportPcapButton.setDisable(notSelected); editPacketBtn.setDisable(notSelected); deleteButtonBtn.setDisable(notSelected); }); streamPacketTableView.addEventFilter(KeyEvent.KEY_RELEASED, (KeyEvent event) -> { if (copyCombination.match(event)) { selectedProfile = tabledata.getProfiles() .get(streamPacketTableView.getSelectionModel().getSelectedIndex()); } else if (pasteCombination.match(event)) { handleDuplicateStream(); } else if (event.getCode() == KeyCode.DELETE) { handleDeletePacket(); } }); streamPacketTableView.setEditable(true); initializeTableColumn(); // disable table reordering streamPacketTableView.widthProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> source, Number oldWidth, Number newWidth) { TableHeaderRow header = (TableHeaderRow) streamPacketTableView.lookup("TableHeaderRow"); header.reorderingProperty().addListener(new ChangeListener<Boolean>() { @Override public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { header.setReordering(false); } }); } }); getChildren().add(streamPacketTableView); setTopAnchor(streamPacketTableView, 35d); setBottomAnchor(streamPacketTableView, 5d); setLeftAnchor(streamPacketTableView, 0d); setRightAnchor(streamPacketTableView, 0d); } /** * Initialize stream action buttons * * @param button * @param disable */ private void initializeStreamButtons(Button button, boolean disable) { button.setDisable(disable); button.setOnAction(this); } /** * Handle stream button click event * * @param event */ @Override public void handle(ActionEvent event) { StreamTableButton source = (StreamTableButton) event.getSource(); handleStreamTableAction(source.getButtonActionType()); } /** * Handle stream table action * * @param action */ private void handleStreamTableAction(StreamTableAction action) { switch (action) { case ADD: viewStreamNameWindow(StreamBuilderType.ADD_STREAM); break; case BUILD: viewStreamNameWindow(StreamBuilderType.BUILD_STREAM); break; case EDIT: handleEditPacket(); break; case DELETE: handleDeletePacket(); break; case IMPORT_PCAP: hanldeImportPcap(); break; case EXPORT_TO_PCAP: handleExportPcapFile(); break; case EXPORT_TO_YAML: handleExportToYaml(); break; } } /** * Handle Add packet button clicked * * @param streamName * @param type */ public void handleAddPacket(String streamName, StreamBuilderType type) { doUpdate = false; TableProfileStream newRow = new TableProfileStream(); newRow.setName(streamName); tabledata.getStreamsList().add(newRow); Profile newProfile = new Profile(); newProfile.setName(streamName); tabledata.getProfiles().add(newProfile); streamPacketTableView.setItems(FXCollections.observableArrayList(tabledata.getStreamsList())); streamPacketTableView.getSelectionModel().select(newRow); openStreamDialog(type); } /** * Handle edit button clicked */ public void handleEditPacket() { openStreamDialog(StreamBuilderType.EDIT_STREAM); } /** * Handle delete button clicked */ public void handleDeletePacket() { try { List<Profile> removedProfileList = new ArrayList<>(); if (Util.isConfirmed("Are you sure you want to delete this streams?") && canDeleteStreams()) { for (int index : streamPacketTableView.getSelectionModel().getSelectedIndices()) { removedProfileList.add(tabledata.getProfiles().get(index)); } tabledata.getStreamsList().removeAll(streamPacketTableView.getSelectionModel().getSelectedItems()); tabledata.getProfiles().removeAll(removedProfileList); saveChangesToYamlFile(tabledata.getProfiles().toArray(new Profile[tabledata.getProfiles().size()])); if (tableUpdateHandler != null) { tableUpdateHandler.onStreamTableChanged(); } } } catch (IOException ex) { LOG.error("Error deleting stream", ex); } } /** * Check whether selected stream is linked to another streams * @return */ private boolean canDeleteStreams() { boolean safeDelete = true; for (TableProfileStream selectedStream : streamPacketTableView.getSelectionModel().getSelectedItems()) { for (Profile profile : tabledata.getProfiles()) { if (profile.getNext().equals(selectedStream.getName())) { return Util.isConfirmed("Some streams are linked to others. Do you want to continue?"); } } } return safeDelete; } /** * Export stream to pcap file */ public void handleExportPcapFile() { try { Profile p = tabledata.getProfiles().get(streamPacketTableView.getSelectionModel().getSelectedIndex()); String packetBinary = p.getStream().getPacket().getBinary(); byte[] pkt = Base64.decodeBase64(packetBinary); Packet packet = EthernetPacket.newPacket(pkt, 0, pkt.length); File pcapFile = File.createTempFile("temp-file-name", ".pcap"); PcapHandle handle = Pcaps.openDead(DataLinkType.EN10MB, 65536); PcapDumper dumper = handle.dumpOpen(pcapFile.getAbsolutePath()); Timestamp ts = new Timestamp(0); dumper.dump(packet, ts); dumper.close(); handle.close(); String fileName = p.getName() + ".pcap"; Window owner = streamPacketTableView.getScene().getWindow(); FileManager.exportFile("Save Pcap File", fileName, pcapFile, owner, FileType.PCAP); } catch (IllegalRawDataException | IOException | PcapNativeException | NotOpenException ex) { LOG.error("Error during generate JSON file", ex); } } /** * Handle import pcap */ private void hanldeImportPcap() { try { // open import dialog setStreamEditingWindowOpen(true); Stage owner = (Stage) streamPacketTableView.getScene().getWindow(); DialogWindow importPcapWindow = new DialogWindow("ImportPcapWizard.fxml", " Import Pcap", 60, 80, false, owner); ImportPcapWizardController importController = (ImportPcapWizardController) importPcapWindow .getController(); importController.initWizard(tabledata.getProfiles(), tabledata.getYamlFileName()); importPcapWindow.show(true); } catch (Exception ex) { LOG.error("Error loading pcap file", ex); } } /** * Handle duplicate stream */ public void handleDuplicateStream() { try { if (selectedProfile != null) { Profile clonedProfile = (Profile) selectedProfile.clone(); clonedProfile.setName(selectedProfile.getName() + "_" + Util.getRandomNumericID(3)); tabledata.getProfiles().add(clonedProfile); Profile[] newProfileDataList = tabledata.getProfiles() .toArray(new Profile[tabledata.getProfiles().size()]); tabledata.setStreamsList(trafficProfile.convertProfilesToTableData(newProfileDataList)); saveChangesToYamlFile(newProfileDataList); } } catch (CloneNotSupportedException | IOException ex) { LOG.error("Error duplicationg stream", ex); } } /** * Save profile changes to yaml file * * @param profiles * @throws JsonProcessingException * @throws IOException */ private void saveChangesToYamlFile(Profile[] profiles) throws JsonProcessingException, IOException { String yamlData = trafficProfile.convertTrafficProfileToYaml(profiles); FileUtils.writeStringToFile(new File(tabledata.getYamlFileName()), yamlData); streamPacketTableView.setItems(FXCollections.observableArrayList(tabledata.getStreamsList())); if (tableUpdateHandler != null && doUpdate) { tableUpdateHandler.onStreamUpdated(); } } /** * initialize packet table columns */ private void initializeTableColumn() { // add enable column streamPacketTableView.getColumns().add(createStaticTableColumn("", "enabledProperty", 30, true)); // index column streamPacketTableView.getColumns().add(createStaticTableColumn("Index", "indexProperty", 50, false)); streamPacketTableView.getColumns().add(createTableColumn("Name", "nameProperty", .17)); streamPacketTableView.getColumns().add(createTableColumn("Packet Type", "packetTypeProperty", .25)); TableColumn lengthColumn = createTableColumn("Length", "lengthProperty", .115); lengthColumn.setId("alignedColumn"); streamPacketTableView.getColumns().add(lengthColumn); streamPacketTableView.getColumns().add(createTableColumn("Mode", "modeProperty", .175)); TableColumn rateColumn = createTableColumn("Rate", "rateProperty", .115); rateColumn.setId("alignedColumn"); streamPacketTableView.getColumns().add(rateColumn); TableColumn nextStreamCol = createTableColumn("Next Stream", "nextStreamProperty", .174); nextStreamCol.setCellFactory(new ImageCellFactory()); streamPacketTableView.getColumns().add(nextStreamCol); } /** * Create and return table column * * @param title * @param propertyName * @param width * @return */ private TableColumn createTableColumn(String title, String propertyName, double width) { TableColumn col = new TableColumn(title); col.prefWidthProperty().bind((streamPacketTableView.widthProperty().subtract(86)).multiply(width)); col.setResizable(false); col.setSortable(false); col.setEditable(false); col.setCellValueFactory(new PropertyValueFactory<>(propertyName)); return col; } /** * Create and return static width table column * * @param title * @param propertyName * @param width * @param hasCheckbox * @return */ private TableColumn createStaticTableColumn(String title, String propertyName, double width, boolean hasCheckbox) { TableColumn col = new TableColumn(title); col.setPrefWidth(width); col.setResizable(false); col.setEditable(false); col.setSortable(false); col.setCellValueFactory(new PropertyValueFactory<>(propertyName)); if (hasCheckbox) { col.setEditable(true); col.setCellFactory(new CheckBoxTableViewCell(this)); } return col; } /** * Return table packet data * * @return */ public List<TableProfileStream> getPacketDataList() { return tabledata.getStreamsList(); } /** * Set table packet data * * @param tableData */ public void setPacketData(TableProfile tableData) { this.tabledata = tableData; numOfStreamLoaded = 0; doUpdate = false; updateNumberOfEnabledStream(); streamPacketTableView.setItems(FXCollections.observableArrayList(tableData.getStreamsList())); } private void updateNumberOfEnabledStream() { numOfEnabledStream = 0; for (Profile profile : tabledata.getProfiles()) { if (profile.getStream().isEnabled()) { numOfEnabledStream++; } } } /** * * @return */ public boolean isStreamEditingWindowOpen() { return streamEditingWindowOpen; } /** * * @param streamEditingWindowOpen */ public void setStreamEditingWindowOpen(boolean streamEditingWindowOpen) { this.streamEditingWindowOpen = streamEditingWindowOpen; } /** * Handle table row click event * * @param event */ public void handleTableRowClick(MouseEvent event) { streamPacketTableView.setContextMenu(null); if (event.getClickCount() == 2 && streamPacketTableView.getSelectionModel().getSelectedItem() != null) { openStreamDialog(StreamBuilderType.EDIT_STREAM); } else if (event.getButton() == MouseButton.SECONDARY && streamPacketTableView.getSelectionModel().getSelectedItem() != null) { streamPacketTableView.setContextMenu(rightClickMenu); } } /** * open stream properties */ private void openStreamDialog(StreamBuilderType type) { try { TableProfileStream data = streamPacketTableView.getSelectionModel().getSelectedItem(); if ("0".equals(data.getLength()) && !Util.isConfirmed("Problem reading file, Do you want to continue anyway ?")) { return; } setStreamEditingWindowOpen(true); Stage currentStage = (Stage) streamPacketTableView.getScene().getWindow(); String windowTitle = "Edit Stream (" + data.getName() + ")"; DialogWindow srteamWindow = new DialogWindow("PacketBuilderHome.fxml", windowTitle, 40, 30, false, currentStage); PacketBuilderHomeController controller = (PacketBuilderHomeController) srteamWindow.getController(); switch (type) { case EDIT_STREAM: controller.initStreamBuilder(data.getPcapBinary(), tabledata.getProfiles(), streamPacketTableView.getSelectionModel().getSelectedIndex(), tabledata.getYamlFileName(), StreamBuilderType.EDIT_STREAM); break; case ADD_STREAM: controller.initStreamBuilder(null, tabledata.getProfiles(), streamPacketTableView.getSelectionModel().getSelectedIndex(), tabledata.getYamlFileName(), StreamBuilderType.ADD_STREAM); break; case BUILD_STREAM: controller.initStreamBuilder(null, tabledata.getProfiles(), streamPacketTableView.getSelectionModel().getSelectedIndex(), tabledata.getYamlFileName(), StreamBuilderType.BUILD_STREAM); break; default: break; } srteamWindow.show(true); } catch (IOException ex) { LOG.error("Error opening file", ex); } } /** * reset table data */ public void reset() { tabledata = null; streamPacketTableView.getItems().clear(); } /** * View stream name window * * @param type */ private void viewStreamNameWindow(StreamBuilderType type) { try { Stage currentStage = (Stage) streamPacketTableView.getScene().getWindow(); DialogWindow profileNameWindow = new DialogWindow("ProfileStreamNameDialog.fxml", "Add Stream", 150, 100, false, currentStage); ProfileStreamNameDialogController controller = (ProfileStreamNameDialogController) profileNameWindow .getController(); controller.setProfileList(tabledata.getProfiles()); controller.setProfileWindow(false); profileNameWindow.show(true); if (controller.isDataAvailable()) { String streamName = controller.getName(); handleAddPacket(streamName, type); } } catch (IOException ex) { LOG.error("Error adding new stream", ex); } } /** * Load stream table data * * @param fileToLoad * @return * @throws Exception */ public Profile[] loadStreamTable(File fileToLoad) throws Exception { this.loadedProfile = fileToLoad; doUpdate = false; Profile[] profileList; this.profileName = fileToLoad.getName(); try { profileList = trafficProfile.getTrafficProfile(fileToLoad); } catch (IOException ex) { LOG.warn("Profile does not have any streams"); profileList = new Profile[0]; } List<TableProfileStream> packetDataList = trafficProfile.convertProfilesToTableData(profileList); TableProfile tableData = new TableProfile(new ArrayList<>(Arrays.asList(profileList)), packetDataList, fileToLoad.getPath()); setPacketData(tableData); return profileList; } /** * Export current load profile to yaml */ private void handleExportToYaml() { Window owner = this.getScene().getWindow(); Profile[] profilesList = tabledata.getProfiles().toArray(new Profile[tabledata.getProfiles().size()]); trafficProfile.exportProfileToYaml(owner, profilesList, profileName); } /** * * @param index * @param newValue */ @Override public void stateChanged(int index, boolean newValue) { try { if (!streamEditingWindowOpen) { if (++numOfStreamLoaded > numOfEnabledStream) { doUpdate = true; } tabledata.getProfiles().get(index).getStream().setEnabled(newValue); saveChangesToYamlFile(tabledata.getProfiles().toArray(new Profile[tabledata.getProfiles().size()])); loadStreamTable(loadedProfile); } } catch (Exception ex) { LOG.error("Error updating profile", ex); } } /** * Add menu item to table rightclickMenu * * @param action */ private void addMenuItem(StreamTableAction action) { MenuItem item = new MenuItem(action.getTitle()); item.setOnAction(new EventHandler<ActionEvent>() { @Override public void handle(ActionEvent event) { handleStreamTableAction(action); } }); rightClickMenu.getItems().add(item); } /** * Class present cell with image factory */ private class ImageCellFactory implements Callback<TableColumn, TableCell> { @Override public TableCell call(TableColumn p) { return new TableCell<Object, String>() { private final ImageView imageview; { imageview = new ImageView(); imageview.setFitHeight(16); imageview.setFitWidth(16); imageview.setCache(true); } @Override protected void updateItem(String item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { imageview.setImage(new Image(item)); setGraphic(imageview); } } }; } } }