edu.mit.lib.handbag.Controller.java Source code

Java tutorial

Introduction

Here is the source code for edu.mit.lib.handbag.Controller.java

Source

/**
 * Copyright 2014 MIT Libraries
 * Licensed under: http://www.apache.org/licenses/LICENSE-2.0
 */
package edu.mit.lib.handbag;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.*;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.FileVisitResult;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.controlsfx.control.PropertySheet;

import com.fasterxml.jackson.jr.ob.JSON;

import edu.mit.lib.bagit.Filler;

public class Controller {

    @FXML
    private Label workflowLabel;
    @FXML
    private ChoiceBox<Workflow> workflowChoiceBox;
    @FXML
    private Label bagLabel;
    @FXML
    private Label bagSizeLabel;
    @FXML
    private Button sendButton;
    @FXML
    private Button trashButton;
    @FXML
    private TreeView<PathRef> payloadTreeView;
    @FXML
    private PropertySheet metadataPropertySheet;

    // flags for state of metadata editing
    // - clean means no edits have been performed
    // - gooey means at least one sticky field has been (re)assigned
    // - complete means all non-optional properties have values
    private boolean cleanMetadata = true;
    private boolean gooeyMetadata = false;
    private boolean completeMetadata = false;
    private long bagSize = 0L;
    private String bagName;
    private int counter = 0;
    private String agent = "anon";
    private Map<String, String> appProps;
    private StringBuilder relPathSB;

    public void setAgent(String agent) {
        this.agent = agent;
        Event.fireEvent(workflowChoiceBox, new ActionEvent("agent", null));
    }

    public void setAppProperties(Map<String, String> props) {
        appProps = props;
    }

    public void initialize() {

        Image wfIm = new Image(getClass().getResourceAsStream("/SiteMap.png"));
        workflowLabel.setGraphic(new ImageView(wfIm));
        workflowLabel.setContentDisplay(ContentDisplay.BOTTOM);

        workflowChoiceBox.addEventHandler(ActionEvent.ACTION, event -> {
            if (workflowChoiceBox.getItems().size() != 0) {
                return;
            }
            ObservableList<Workflow> wflowList = FXCollections.observableArrayList(loadWorkflows());
            workflowChoiceBox.setItems(wflowList);
            workflowChoiceBox.getSelectionModel().selectedIndexProperty().addListener((ov, value, new_value) -> {
                int newVal = (int) new_value;
                if (newVal != -1) {
                    Workflow newSel = workflowChoiceBox.getItems().get(newVal);
                    if (newSel != null) {
                        workflowLabel.setText(newSel.getName());
                        generateBagName(newSel.getBagNameGenerator());
                        bagLabel.setText(bagName);
                        sendButton.setText(newSel.getDestinationName());
                        setMetadataList(newSel);
                    }
                }
            });
        });

        Image bagIm = new Image(getClass().getResourceAsStream("/Bag.png"));
        bagLabel.setGraphic(new ImageView(bagIm));
        bagLabel.setContentDisplay(ContentDisplay.BOTTOM);

        Image sendIm = new Image(getClass().getResourceAsStream("/Cabinet.png"));
        sendButton.setGraphic(new ImageView(sendIm));
        sendButton.setContentDisplay(ContentDisplay.BOTTOM);
        sendButton.setDisable(true);
        sendButton.setOnAction(e -> transmitBag());

        Image trashIm = new Image(getClass().getResourceAsStream("/Bin.png"));
        trashButton.setGraphic(new ImageView(trashIm));
        trashButton.setContentDisplay(ContentDisplay.BOTTOM);
        trashButton.setDisable(true);
        trashButton.setOnAction(e -> reset(false));

        TreeItem<PathRef> rootItem = new TreeItem<>(new PathRef("", Paths.get("data")));
        rootItem.setExpanded(true);
        payloadTreeView.setRoot(rootItem);
        payloadTreeView.setOnDragOver(event -> {
            if (event.getGestureSource() != payloadTreeView && event.getDragboard().getFiles().size() > 0) {
                event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
            }
            event.consume();
        });
        payloadTreeView.setOnDragDropped(event -> {
            Dragboard db = event.getDragboard();
            boolean success = false;
            if (db.getFiles().size() > 0) {
                for (File dragFile : db.getFiles()) {
                    if (dragFile.isDirectory()) {
                        // explode directory and add expanded relative paths to tree
                        relPathSB = new StringBuilder();
                        try {
                            Files.walkFileTree(dragFile.toPath(), new SimpleFileVisitor<Path>() {
                                @Override
                                public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                                        throws IOException {
                                    relPathSB.append(dir.getFileName()).append("/");
                                    return FileVisitResult.CONTINUE;
                                }

                                @Override
                                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
                                        throws IOException {
                                    payloadTreeView.getRoot().getChildren()
                                            .add(new TreeItem<>(new PathRef(relPathSB.toString(), file)));
                                    bagSize += Files.size(file);
                                    return FileVisitResult.CONTINUE;
                                }
                            });
                        } catch (IOException ioe) {
                        }
                    } else {
                        payloadTreeView.getRoot().getChildren()
                                .add(new TreeItem<>(new PathRef("", dragFile.toPath())));
                        bagSize += dragFile.length();
                    }
                }
                bagSizeLabel.setText(scaledSize(bagSize, 0));
                success = true;
                updateButtons();
            }
            event.setDropCompleted(success);
            event.consume();
        });

        metadataPropertySheet.setModeSwitcherVisible(false);
        metadataPropertySheet.setDisable(false);
        metadataPropertySheet.addEventHandler(KeyEvent.ANY, event -> {
            checkComplete(metadataPropertySheet.getItems());
            event.consume();
        });
    }

    private void checkComplete(List<PropertySheet.Item> mdList) {
        boolean curComplete = true;
        for (PropertySheet.Item item : mdList) {
            MetadataItem mdItem = (MetadataItem) item;
            String value = mdItem.getValue();
            if (!mdItem.isOptional() && (value == null || value.equals(""))) {
                curComplete = false;
                break;
            }
        }
        cleanMetadata = false;
        completeMetadata = curComplete;
        updateButtons();
    }

    private void transmitBag() {
        try {
            URI destUri = new URL(workflowChoiceBox.getValue().getDestinationUrl()).toURI();
            String pkgFormat = workflowChoiceBox.getValue().getPackageFormat();
            boolean localDest = destUri.getScheme().startsWith("file");
            Path destDir = Paths.get(destUri);
            Filler filler = localDest ? new Filler(destDir.resolve(bagName)) : new Filler();
            // add payload files
            for (TreeItem ti : payloadTreeView.getRoot().getChildren()) {
                PathRef pr = (PathRef) ti.getValue();
                if (pr.getRelPath().length() > 0) {
                    filler.payload(pr.getRelPath() + pr.getPath().getFileName(), pr.getPath());
                } else {
                    filler.payload(pr.getPath());
                }
            }
            // add metadata (currently only bag-info properties supported)
            for (PropertySheet.Item mdItem : metadataPropertySheet.getItems()) {
                MetadataItem item = (MetadataItem) mdItem;
                if (item.getValue() != null && item.getValue().length() > 0) {
                    filler.metadata(item.getRealName(), item.getValue());
                }
            }
            if (localDest) {
                filler.toPackage(pkgFormat);
            } else {
                // send to URL - TODO
            }
            reset(true);
        } catch (IOException | URISyntaxException exp) {
        }
    }

    // reset payload and metadata to ready state (respecting stickiness if requested)
    private void reset(boolean keepStickies) {
        payloadTreeView.getRoot().getChildren().clear();
        ObservableList<PropertySheet.Item> items = metadataPropertySheet.getItems();
        List<PropertySheet.Item> saveItems = new ArrayList<>();
        saveItems.addAll(items);
        items.clear();
        cleanMetadata = true;
        gooeyMetadata = false;
        boolean anyNeeded = false;
        int i = 0;
        for (MetadataSpec spec : workflowChoiceBox.getValue().getMetadata()) {
            if (!spec.isOptional() && spec.needsValue()) {
                anyNeeded = true;
            }
            MetadataItem item = new MetadataItem(spec);
            String oldValue = (String) saveItems.get(i).getValue();
            if (spec.isSticky() && keepStickies && oldValue != null) {
                item.setValue(oldValue);
                gooeyMetadata = true;
            }
            items.add(item);
            i++;
        }
        completeMetadata = !anyNeeded;
        updateButtons();
        bagSize = 0L;
        bagSizeLabel.setText("[empty]");
        generateBagName(workflowChoiceBox.getValue().getBagNameGenerator());
        bagLabel.setText(bagName);
    }

    // check and update disabled state of buttons based on application state
    private void updateButtons() {
        boolean empty = payloadTreeView.getRoot().getChildren().size() == 0;
        trashButton.setDisable(empty && cleanMetadata && !gooeyMetadata);
        sendButton.setDisable(empty || !completeMetadata);
        workflowChoiceBox.setDisable(!(empty && cleanMetadata));
    }

    private void setMetadataList(Workflow wf) {
        ObservableList<PropertySheet.Item> items = metadataPropertySheet.getItems();
        items.clear();
        boolean anyNeeded = false;
        for (MetadataSpec spec : wf.getMetadata()) {
            if (!spec.isOptional() && spec.needsValue()) {
                anyNeeded = true;
            }
            items.add(new MetadataItem(spec));
        }
        cleanMetadata = true;
        completeMetadata = !anyNeeded;
    }

    private List<Workflow> loadWorkflows() {
        // get list of agent-specific installed workflows first, then load each one.
        // It there is dispatcher web service, use it, else look in local JSON resource files
        String dispatcherUrl = appProps.get("dispatcher");
        List<Workflow> workflows = new ArrayList<>();
        try {
            List<String> workflowIds = getWorkflowIds(dispatcherUrl);
            for (String wfFile : workflowIds) {
                Workflow wf = null;
                if (dispatcherUrl != null) {
                    try (InputStream in = new URL(dispatcherUrl).openConnection().getInputStream()) {
                        wf = JSON.std.beanFrom(Workflow.class, in);
                    }
                } else {
                    wf = JSON.std.beanFrom(Workflow.class, getClass().getResourceAsStream("/" + wfFile));
                }
                workflows.add(wf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return workflows;
    }

    private List<String> getWorkflowIds(String dispatcherUrl) throws IOException {
        List<String> wfIdList = null;
        if (dispatcherUrl == null) {
            wfIdList = JSON.std.listOfFrom(String.class, getClass().getResourceAsStream("/" + agent + ".json"));
        } else {
            try (InputStream in = new URL(dispatcherUrl).openConnection().getInputStream()) {
                wfIdList = JSON.std.listOfFrom(String.class, in);
            }
        }
        return wfIdList;
    }

    private void generateBagName(String generator) {
        bagName = "transfer." + counter++;
    }

    static class PathRef {
        private String relPath;
        private Path path;

        public PathRef(String relPath, Path path) {
            this.relPath = relPath;
            this.path = path;
        }

        public String getRelPath() {
            return relPath;
        }

        public Path getPath() {
            return path;
        }

        public String toString() {
            return relPath + path.toFile().getName();
        }
    }

    private static final String[] tags = { "bytes", "KB", "MB", "GB", "TB" };

    static String scaledSize(long size, int index) {
        if (size < 1000) {
            return size + " " + tags[index];
        } else {
            return scaledSize(size / 1000, index + 1);
        }
    }
}