life.qbic.components.PackageManagerTab.java Source code

Java tutorial

Introduction

Here is the source code for life.qbic.components.PackageManagerTab.java

Source

/*******************************************************************************
 * QBiC Offer Generator provides an infrastructure for creating offers using QBiC portal and
 * infrastructure. Copyright (C) 2017 Aydn Can Polatkan, 2018 Benjamin Sailer
 *
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU General Public License as published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this program. If
 * not, see http://www.gnu.org/licenses/.
 *******************************************************************************/

package life.qbic.components;

import com.vaadin.data.Item;
import com.vaadin.data.fieldgroup.FieldGroup;
import com.vaadin.data.util.GeneratedPropertyContainer;
import com.vaadin.data.util.sqlcontainer.SQLContainer;
import com.vaadin.data.util.sqlcontainer.query.TableQuery;
import com.vaadin.server.*;
import com.vaadin.shared.data.sort.SortDirection;
import com.vaadin.ui.*;
import life.qbic.dbase.DBManager;
import life.qbic.dbase.Database;
import life.qbic.utils.RefreshableGrid;
import org.vaadin.gridutil.cell.GridCellFilter;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.SQLException;
import java.util.Arrays;

import static life.qbic.components.OfferManagerTab.getPathOnServer;
import static life.qbic.utils.qOfferManagerUtils.createExportContent;
import static life.qbic.utils.qOfferManagerUtils.displayNotification;

final class PackageManagerTab {

    // price modifiers for project management and bioinformatics analysis
    private static BigDecimal externalAcademicPriceModifier = new BigDecimal("1.3");
    private static BigDecimal externalCommercialPriceModifier = new BigDecimal("2.0");

    // price modifier for sequencing and mass spectrometry packages
    private static BigDecimal externalPriceModifier = new BigDecimal("1.1");

    private static FileDownloader fileDownloader;
    private static String pathOnServer = getPathOnServer();

    /**
     * creates the tab for creating new packages
     * @return vaadin component
     * @throws SQLException:
     */
    static Component createPackageManagerTab() throws SQLException {

        Database db = qOfferManager.getDb();

        VerticalLayout packManVerticalLayout = new VerticalLayout();
        packManVerticalLayout.setMargin(true);
        packManVerticalLayout.setSpacing(true);
        packManVerticalLayout.setSizeFull();

        HorizontalLayout packManHorizontalLayout = new HorizontalLayout();
        packManHorizontalLayout.setSpacing(true);

        Button addPackageButton = new Button("New Package");
        addPackageButton.setIcon(FontAwesome.PLUS);
        addPackageButton.setDescription("Click here to add a new package but don't forget to update the details.");

        ComboBox updatePackageGroupComboBox = new ComboBox("Select package group");
        updatePackageGroupComboBox.addItems("Sequencing", "Project Management", "Bioinformatics Analysis",
                "Mass spectrometry", "Other");
        updatePackageGroupComboBox.setDescription(
                "Select a package group for the currently selected package and hit the " + "update button.");

        Button updateSelectedPackageButton = new Button("Update");
        updateSelectedPackageButton.setIcon(FontAwesome.SPINNER);
        updateSelectedPackageButton.setDescription("Click here to update the currently selected package.");

        Button deleteSelectedPackageButton = new Button("Delete");
        deleteSelectedPackageButton.setIcon(FontAwesome.TRASH_O);
        deleteSelectedPackageButton.setDescription("Click here to delete the currently selected package.");

        Button exportTableButton = new Button("Export as .csv");
        exportTableButton.setIcon(FontAwesome.DOWNLOAD);
        exportTableButton.setDescription("Click here to export the table as .csv file.");

        CheckBox calculatePricesAutomaticallyCheckBox = new CheckBox("Auto-calculate external prices");
        calculatePricesAutomaticallyCheckBox
                .setDescription("Click here to enable/disable the automatic calculation of the "
                        + "external prices based on the internal prices.");
        calculatePricesAutomaticallyCheckBox.setValue(true);

        TableQuery tq = new TableQuery("packages", DBManager.getDatabaseInstanceAlternative());
        tq.setVersionColumn("OPTLOCK");

        SQLContainer container = new SQLContainer(tq);
        container.setAutoCommit(true);

        GeneratedPropertyContainer gpcontainer = new GeneratedPropertyContainer(container);

        // create the column holding the offer_ids where the package is being used in
        // TODO: offer_ids currently not in use; since it's extremely slow..
        /*
            gpcontainer.addGeneratedProperty("offer_ids",
                new PropertyValueGenerator<String>() {
                  @Override
                  public String getValue(Item item, Object itemId,
                             Object propertyId) {
            
        int package_id = (Integer) item.getItemProperty("package_id").getValue();
            
        // query offers_packages for all offer_ids of the current package
        ArrayList<String> offerIds = db.getOfferIdsForPackage(package_id);
            
        return String.join(",", offerIds);
                  }
            
                  @Override
                  public Class<String> getType() {
        return String.class;
                  }
                });
        */

        RefreshableGrid packageGrid = new RefreshableGrid(gpcontainer);

        // add the filters to the grid
        GridCellFilter filter = new GridCellFilter(packageGrid);
        filter.setTextFilter("package_id", true, true);
        filter.setTextFilter("package_name", true, false);
        filter.setTextFilter("package_facility", true, false);
        filter.setTextFilter("package_description", true, false);
        filter.setTextFilter("package_group", true, false);
        filter.setTextFilter("package_unit_type", true, false);
        filter.setComboBoxFilter("package_group", Arrays.asList("Bioinformatics Analysis", "Mass spectrometry",
                "Project Management", "Sequencing", "Other"));

        packageGrid.getColumn("package_id").setHeaderCaption("Id").setWidth(100);
        packageGrid.getColumn("package_name").setHeaderCaption("Name");
        packageGrid.getColumn("package_facility").setHeaderCaption("Facility");
        packageGrid.getColumn("package_description").setHeaderCaption("Description").setWidth(300);
        packageGrid.getColumn("package_group").setHeaderCaption("Package Group").setEditable(false);
        packageGrid.getColumn("package_price_internal").setHeaderCaption("Internal Price ()");
        packageGrid.getColumn("package_price_external_academic").setHeaderCaption("Ext. Academical Price ()");
        packageGrid.getColumn("package_price_external_commercial").setHeaderCaption("Ext. Commercial Price ()");
        // TODO: offer_ids currently not in use; since it's extremely slow..
        //packageGrid.getColumn("offer_ids").setHeaderCaption("Offer Id's");
        packageGrid.getColumn("package_unit_type").setHeaderCaption("Unit Type");

        /*
        // TODO: offer_ids currently not in use; since it's extremely slow..
        packageGrid.setColumnOrder("package_id", "package_name", "package_description", "package_group", "package_facility",
            "package_price_internal", "package_price_external_academic", "package_price_external_commercial",
            "package_unit_type", "offer_ids");*/

        packageGrid.setColumnOrder("package_id", "package_name", "package_description", "package_group",
                "package_facility", "package_price_internal", "package_price_external_academic",
                "package_price_external_commercial", "package_unit_type");

        packageGrid.removeColumn("added_by");
        packageGrid.removeColumn("package_grp");
        packageGrid.removeColumn("package_date");
        packageGrid.removeColumn("last_edited");

        packageGrid.sort("package_name", SortDirection.ASCENDING);
        packageGrid.setWidth("100%");
        packageGrid.setSelectionMode(Grid.SelectionMode.SINGLE);
        packageGrid.setEditorEnabled(true);

        addAutomaticPriceCalculation(calculatePricesAutomaticallyCheckBox, container, packageGrid);

        // add tooltips to the cells
        packageGrid.setCellDescriptionGenerator((Grid.CellDescriptionGenerator) cell -> {
            if (cell.getValue() == null)
                return null;
            return cell.getValue().toString();
        });

        // add tooltips to the header row
        for (Grid.Column column : packageGrid.getColumns()) {
            Grid.HeaderCell cell = packageGrid.getDefaultHeaderRow().getCell(column.getPropertyId());
            String htmlWithTooltip = String.format("<span title=\"%s\">%s</span>", cell.getText(), cell.getText());
            cell.setHtml(htmlWithTooltip);
        }

        addListeners(db, addPackageButton, updatePackageGroupComboBox, updateSelectedPackageButton,
                deleteSelectedPackageButton, container, packageGrid, exportTableButton);

        packManHorizontalLayout.addComponent(addPackageButton);
        packManHorizontalLayout.addComponent(updatePackageGroupComboBox);
        packManHorizontalLayout.addComponent(updateSelectedPackageButton);
        packManHorizontalLayout.addComponent(deleteSelectedPackageButton);
        packManHorizontalLayout.addComponent(exportTableButton);
        packManHorizontalLayout.addComponent(calculatePricesAutomaticallyCheckBox);

        packManHorizontalLayout.setComponentAlignment(addPackageButton, Alignment.BOTTOM_CENTER);
        packManHorizontalLayout.setComponentAlignment(updatePackageGroupComboBox, Alignment.MIDDLE_CENTER);
        packManHorizontalLayout.setComponentAlignment(updateSelectedPackageButton, Alignment.BOTTOM_CENTER);
        packManHorizontalLayout.setComponentAlignment(deleteSelectedPackageButton, Alignment.BOTTOM_CENTER);
        packManHorizontalLayout.setComponentAlignment(calculatePricesAutomaticallyCheckBox,
                Alignment.MIDDLE_CENTER);
        packManHorizontalLayout.setComponentAlignment(exportTableButton, Alignment.BOTTOM_CENTER);

        packManVerticalLayout.addComponent(packageGrid);
        packManVerticalLayout.addComponent(packManHorizontalLayout);

        return packManVerticalLayout;
    }

    /**
     * adds the automatic price calculation to the grid
     * @param calculatePricesAutomatically: checkbox whether the prices should be automatically calculated or not
     * @param container: sql container for getting the currently selected row
     * @param packageGrid: grid showing the packages
     */
    private static void addAutomaticPriceCalculation(CheckBox calculatePricesAutomatically, SQLContainer container,
            RefreshableGrid packageGrid) {

        // TODO: make this more efficient + not a workaround any more
        // add the automatic price calculation for the external academic and external commercial prices to the package.
        // Since vaadin doesn't provide a proper ValueChangeListener for the grid, we have to do it this way..
        // Note: this recalculates the prices whenever some package has been edited by the user, which is pretty bad,
        // but I couldn't find a better way (a ValueChangeListener for the editor field of the internal_price column did
        // work even worse, so I decided to stick with this..).
        packageGrid.getEditorFieldGroup().addCommitHandler(new FieldGroup.CommitHandler() {
            @Override
            public void preCommit(FieldGroup.CommitEvent commitEvent) throws FieldGroup.CommitException {

            }

            @Override
            public void postCommit(FieldGroup.CommitEvent commitEvent) throws FieldGroup.CommitException {

                if (calculatePricesAutomatically.getValue()) {

                    // Get the internal package price from the selected row in the grid
                    Object selected = ((Grid.SingleSelectionModel) packageGrid.getSelectionModel())
                            .getSelectedRow();
                    if (selected == null) {
                        displayNotification("Price could not be automatically calculated", "Vaadin couldn't get "
                                + "the selected row, so the external prices have NOT been automatically calculated. If you wanted to "
                                + "update the prices, please select the row via left click and then try shift + enter to open the edit "
                                + "menu. Unfortunately this seems to be a little buggy..", "warning");
                        return;
                    }
                    Item selectedRow = container.getItem(selected);
                    String packagePriceInternalString = selectedRow.getItemProperty("package_price_internal")
                            .getValue().toString();

                    // get the internal price as BigDecimal to deal with floating point issues
                    BigDecimal packagePriceInternal = new BigDecimal(packagePriceInternalString);

                    // check if we have a package group for the current package
                    Object packageGroupObject = selectedRow.getItemProperty("package_group").getValue();
                    if (packageGroupObject == null) {
                        displayNotification("No package group", "Package has no package group associated with it. "
                                + "Thus an automatic calculation of the external packages is NOT possible. Please update the package group"
                                + "or disable the Auto-calculate external prices checkbox.", "warning");
                        return;
                    }

                    String packageGroup = packageGroupObject.toString();

                    // based on the package group we have differnet price modifiers:
                    Float packagePriceExternalAcademic;
                    Float packagePriceExternalCommercial;
                    switch (packageGroup) {
                    case "Bioinformatics Analysis":
                    case "Project Management":
                        // recalculate the two external prices (*1.3 and *2.0)
                        packagePriceExternalAcademic = packagePriceInternal.multiply(externalAcademicPriceModifier)
                                .floatValue();
                        packagePriceExternalCommercial = packagePriceInternal
                                .multiply(externalCommercialPriceModifier).floatValue();
                        break;
                    case "Sequencing":
                    case "Mass spectrometry":
                        // recalculate the two external prices (*1.1)
                        packagePriceExternalAcademic = packagePriceInternal.multiply(externalPriceModifier)
                                .floatValue();
                        packagePriceExternalCommercial = packagePriceInternal.multiply(externalPriceModifier)
                                .floatValue();
                        break;
                    default: // package group is "Other"
                        displayNotification("Wrong package group", "Package has package group \"Other\" "
                                + "associated with it. Thus an automatic calculation of the external packages is NOT possible. "
                                + "Please change the package group or disable the Auto-calculate external prices checkbox.",
                                "warning");
                        return;
                    }

                    // set the respective fields in the grid, which also updates the database
                    selectedRow.getItemProperty("package_price_external_academic")
                            .setValue(packagePriceExternalAcademic);
                    selectedRow.getItemProperty("package_price_external_commercial")
                            .setValue(packagePriceExternalCommercial);
                }
            }
        });
    }

    /**
     * adds the listeners to the three buttons
     * @param db: database instance to query
     * @param addPackageButton: button for creating a new package
     * @param updatePackageGroupComboBox: combo box for selecting the package group
     * @param updateSelectedPackageButton: button for updating a package
     * @param deleteSelectedPackageButton: button for deleting a package
     * @param container: SQLContainer holding the data from the database
     * @param packageGrid: grid holding the packages
     * @param exportTableButton: button for exporting the grid as csv
     */
    private static void addListeners(Database db, Button addPackageButton, ComboBox updatePackageGroupComboBox,
            Button updateSelectedPackageButton, Button deleteSelectedPackageButton, SQLContainer container,
            RefreshableGrid packageGrid, Button exportTableButton) {

        addPackageButton.addClickListener(new Button.ClickListener() {

            /**
             *
             */
            private static final long serialVersionUID = 8181926819540586585L;

            @Override
            public void buttonClick(Button.ClickEvent event) {
                DBManager.getDatabaseInstance().addNewPackage("*** New Package - double click to edit ***");
                displayNotification("New Package Added",
                        "Please edit the package details! If the details are not complete, incompatibility "
                                + "issues are expected to happen.",
                        "success");
                packageGrid.clearSortOrder();
                packageGrid.sort("package_name", SortDirection.ASCENDING);
            }
        });

        updateSelectedPackageButton.addClickListener((Button.ClickListener) event -> {

            if (packageGrid.getSelectedRow() == null) {
                displayNotification("oOps! Forgot something?!",
                        "Please make sure that you select a package to update.", "error");
            } else if (updatePackageGroupComboBox.getValue() == null) {
                displayNotification("oOps! Forgot something?!",
                        "Please make sure that you select an option for the package group.", "error");
            } else {
                String selectedPackageGroup = updatePackageGroupComboBox.getValue().toString();
                String packageId = packageGrid.getSelectedRow().toString();
                db.updatePackageGroupForPackage(selectedPackageGroup, packageId);
                container.refresh();
            }
        });

        deleteSelectedPackageButton.addClickListener((Button.ClickListener) event -> {
            Object selectedRow = packageGrid.getSelectedRow();
            if (selectedRow == null) {
                displayNotification("No package selected!", "Please select a package to delete.", "error");
                return;
            }

            int selectedPackageId = (int) packageGrid.getContainerDataSource().getItem(selectedRow)
                    .getItemProperty("package_id").getValue();

            // check if package is used in a offer
            boolean isPackageSelected = db.isPackageSelectedForAnyOffer(selectedPackageId);
            if (isPackageSelected) {
                // get the first offer_id from the offers where the package is in use
                int offerId = db.getFirstOfferIdForPackageId(selectedPackageId);
                displayNotification("Package in use", "Package " + selectedPackageId + " is used by offer "
                        + offerId + " ! Please remove the package from the offer before deleting it.", "error");
                return;
            }

            db.deletePackage(selectedPackageId);
            // since refreshing the rows doesn't work properly; we force an update of the grid by setting the sort direction
            // of the package name column
            packageGrid.sort("package_name", SortDirection.ASCENDING);
            displayNotification("Package deleted", "Package " + selectedPackageId + " successfully deleted.",
                    "success");
        });

        // setup the export as .csv file functionality
        String exportPackagesFileName = pathOnServer + "packages.csv";
        fileDownloader = new FileDownloader(new FileResource(new File(exportPackagesFileName))) {
            @Override
            public boolean handleConnectorRequest(VaadinRequest request, VaadinResponse response, String path)
                    throws IOException {
                createExportContent(container, exportPackagesFileName, fileDownloader);
                return super.handleConnectorRequest(request, response, path);
            }
        };
        fileDownloader.extend(exportTableButton);

    }
}