org.dataconservancy.packaging.gui.presenter.impl.OpenExistingPackagePresenterImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.dataconservancy.packaging.gui.presenter.impl.OpenExistingPackagePresenterImpl.java

Source

package org.dataconservancy.packaging.gui.presenter.impl;

/*
 * Copyright 2015 Johns Hopkins University
 * 
 * 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.
 */

import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Files;

import javafx.application.Platform;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import org.apache.commons.lang.SystemUtils;
import org.dataconservancy.packaging.gui.Errors.ErrorKey;
import org.dataconservancy.packaging.gui.TextFactory;
import org.dataconservancy.packaging.gui.presenter.OpenExistingPackagePresenter;
import org.dataconservancy.packaging.gui.view.OpenExistingPackageView;
import org.dataconservancy.packaging.tool.model.OpenedPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javafx.scene.Node;
import javafx.stage.DirectoryChooser;
import javafx.stage.FileChooser;

public class OpenExistingPackagePresenterImpl extends BasePresenterImpl implements OpenExistingPackagePresenter {
    private OpenExistingPackageView view;
    private DirectoryChooser directoryChooser;
    private FileChooser fileChooser;
    private File selectedFile;
    private FILE_TYPE selectedFileType;
    private File stagingDir;
    private LoadPackageService loadPackageBackgroundService;

    private final Logger log = LoggerFactory.getLogger(this.getClass());

    public OpenExistingPackagePresenterImpl(OpenExistingPackageView view) {
        super(view);
        this.view = view;
        this.directoryChooser = new DirectoryChooser();
        this.fileChooser = new FileChooser();

        view.setPresenter(this);
        bind();

        // Staging directory is working directory by default.
        stagingDir = new File(System.getProperty("user.dir"));

        //If we can't write to the current working directory switch to the java temp dir which we should have write access to
        if (!Files.isWritable(FileSystems.getDefault().getPath(stagingDir.getPath()))) {
            stagingDir = new File(System.getProperty("java.io.tmpdir"));
        }
    }

    private void bind() {
        if (loadPackageBackgroundService == null) {
            loadPackageBackgroundService = new BackgroundPackageService();
        }

        // Background load service called when continue button fired
        loadPackageBackgroundService.setOnSucceeded(t -> {
            if (Platform.isFxApplicationThread()) {
                view.getProgressPopup().hide();
            }

            OpenedPackage pkg = (OpenedPackage) t.getSource().getValue();

            loadPackageBackgroundService.reset();

            controller.setPackageState(pkg.getPackageState());
            controller.setPackageTree(pkg.getPackageTree());

            if (pkg.getBaseDirectory() == null) {
                // Package state was loaded, go to next page.

                controller.setPackageStateFileChooserInitialChoice(selectedFile);
                controller.goToNextPage();
            } else {
                // Package was loaded, save state and go to next page.
                super.onContinuePressed();
            }
        });

        loadPackageBackgroundService.setOnFailed(workerStateEvent -> {
            if (Platform.isFxApplicationThread()) {
                view.getProgressPopup().hide();
            }

            if (workerStateEvent.getSource().getMessage() == null
                    || workerStateEvent.getSource().getMessage().isEmpty()) {
                Throwable e = workerStateEvent.getSource().getException();
                showError(TextFactory.format(ErrorKey.PACKAGE_STATE_LOAD_ERROR, e.getMessage()));
                log.error(e.getMessage());
            } else {
                showError(workerStateEvent.getSource().getMessage());
            }

            loadPackageBackgroundService.reset();
        });

        loadPackageBackgroundService.setOnCancelled(workerStateEvent -> {
            if (Platform.isFxApplicationThread()) {
                view.getProgressPopup().hide();
            }

            showError(workerStateEvent.getSource().getMessage());

            loadPackageBackgroundService.reset();
        });

        // User changes staging directory
        view.getChoosePackageStagingDirectoryButton().setOnAction(event -> {
            File file = controller.showOpenDirectoryDialog(directoryChooser);

            if (file == null) {
                return;
            }

            stagingDir = file;
            view.getChoosePackageStagingDirectoryTextField().setText(stagingDir.getPath());
        });

        // User selects an serialized package or a package state
        view.getChoosePackageFileButton().setOnAction(event -> {
            //Disables file extension filtering on mac since it doesn't seem to reliably work
            if (!SystemUtils.IS_OS_MAC) {
                fileChooser.getExtensionFilters().clear();
                fileChooser.getExtensionFilters()
                        .add(new FileChooser.ExtensionFilter("Package File", "*.zip", "*.ZIP", "*.tar", "*.TAR",
                                "*.gz", "*.GZ", "*.gzip", "*.GZIP", controller.getPackageStateFileExtension()));
                fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("All files (*.*)", "*.*"));
            }

            File file = controller.showOpenFileDialog(fileChooser);

            if (file == null) {
                return;
            }

            clear();
            view.getChoosePackageFileTextField().setText(file.getName());
            view.getContinueButton().setDisable(false);

            selectedFile = file;

            // Check if package or state file
            String state_ext = controller.getPackageStateFileExtension().substring(1);

            if (selectedFile.getName().endsWith(state_ext)) {
                selectedFileType = FILE_TYPE.STATE_FILE;
            } else {
                selectedFileType = FILE_TYPE.PACKAGE;
            }
        });

        // User selects an exploded package
        view.getChooseExplodedPackageDirectoryButton().setOnAction(event -> {
            File file = controller.showOpenDirectoryDialog(directoryChooser);

            if (file == null) {
                return;
            }

            clear();
            view.getChooseExplodedPackageDirectoryTextField().setText(file.getName());
            view.getContinueButton().setDisable(false);

            selectedFile = file;
            selectedFileType = FILE_TYPE.EXPLODED_PACKAGE;
        });
    }

    // Needed for testing.
    protected File getSelectedFile() {
        return selectedFile;
    }

    // Needed for testing.
    protected FILE_TYPE getSelectedFileType() {
        return selectedFileType;
    }

    @Override
    public void onContinuePressed() {
        loadPackageBackgroundService.execute(selectedFile, selectedFileType);

        if (Platform.isFxApplicationThread()) {
            view.getProgressPopup().show();
        }
    }

    @Override
    public void onBackPressed() {
        // Do not save state.
        getController().goToPreviousPage();
    }

    @Override
    public void clear() {
        clearError();

        view.getChooseExplodedPackageDirectoryTextField().setText("");
        view.getChoosePackageFileTextField().setText("");
        view.getChoosePackageStagingDirectoryTextField().setText(System.getProperty("user.home"));
        view.getContinueButton().setDisable(true);
    }

    @Override
    public Node display() {
        clear();
        view.getHeaderViewHelpLink().setOnAction(arg0 -> view.showHelpPopup());
        return view.asNode();
    }

    //Method should only be used for testing, will run all code on the same thread to simplify the test.
    protected void setTestBackgroundService() {
        this.loadPackageBackgroundService = new AsyncPackageService();
        bind();
    }

    protected enum FILE_TYPE {
        STATE_FILE, EXPLODED_PACKAGE, PACKAGE
    }

    /*
     * Simple interface that shadows JavaFX service this is used so we can create our own instance to use in testing.
     */
    protected interface LoadPackageService {
        void execute(File file, FILE_TYPE fileType);

        void setOnFailed(EventHandler<WorkerStateEvent> handler);

        void setOnCancelled(EventHandler<WorkerStateEvent> handler);

        void setOnSucceeded(EventHandler<WorkerStateEvent> handler);

        void reset();

        void cancel();
    }

    private class BackgroundPackageService implements LoadPackageService {

        BackgroundService service;

        public BackgroundPackageService() {
            service = new BackgroundService();
        }

        @Override
        public void execute(File file, FILE_TYPE fileType) {
            service.setPackageFile(file, fileType);
            service.start();
        }

        @Override
        public void setOnFailed(EventHandler<WorkerStateEvent> handler) {
            service.setOnFailed(handler);
        }

        @Override
        public void setOnCancelled(EventHandler<WorkerStateEvent> handler) {
            service.setOnCancelled(handler);
        }

        @Override
        public void setOnSucceeded(EventHandler<WorkerStateEvent> handler) {
            service.setOnSucceeded(handler);
        }

        @Override
        public void cancel() {
            service.cancel();
        }

        @Override
        public void reset() {
            service.reset();
        }

        /**
         * A {@link javafx.concurrent.Service} which executes the {@link javafx.concurrent.Task} for validating the node properties in the tree.
         */
        private class BackgroundService extends Service<OpenedPackage> {

            File packageFile;
            FILE_TYPE fileType;

            public BackgroundService() {
            }

            public void setPackageFile(File packageFile, FILE_TYPE fileType) {
                this.packageFile = packageFile;
                this.fileType = fileType;
            }

            @Override
            protected Task<OpenedPackage> createTask() {
                return new Task<OpenedPackage>() {
                    @Override
                    protected OpenedPackage call() throws Exception {
                        switch (fileType) {
                        case STATE_FILE:
                            return controller.getFactory().getOpenPackageService().openPackageState(packageFile);
                        case EXPLODED_PACKAGE:
                            return controller.getFactory().getOpenPackageService().openExplodedPackage(packageFile);
                        case PACKAGE:
                            return controller.getFactory().getOpenPackageService().openPackage(stagingDir,
                                    packageFile);
                        }

                        return null;
                    }
                };
            }
        }
    }

    /*
     * Generate Package Service used for testing, this implementation mimics JavaFX service but operates on the same thread, to make it easier for testing. Also prevents
     * tests from needing to run on the JavaFX application thread.
     */
    private class AsyncPackageService implements LoadPackageService {
        @SuppressWarnings("unused")
        private EventHandler<WorkerStateEvent> cancelledHandler;
        private EventHandler<WorkerStateEvent> successHandler;

        @Override
        public void execute(File packageFile, FILE_TYPE fileType) {
            AsyncWorker worker = new AsyncWorker();

            try {
                switch (fileType) {
                case STATE_FILE:
                    worker.setValue(controller.getFactory().getOpenPackageService().openPackageState(packageFile));
                case EXPLODED_PACKAGE:
                    worker.setValue(
                            controller.getFactory().getOpenPackageService().openExplodedPackage(packageFile));
                case PACKAGE:
                    worker.setValue(
                            controller.getFactory().getOpenPackageService().openPackage(stagingDir, packageFile));
                }

            } catch (Exception e) {
                worker.setState(Worker.State.FAILED);
                worker.setMessage(e.getMessage());
            }
            worker.setState(Worker.State.SUCCEEDED);
            successHandler.handle(new WorkerStateEvent(worker, WorkerStateEvent.WORKER_STATE_SUCCEEDED));
        }

        @Override
        public void setOnFailed(EventHandler<WorkerStateEvent> handler) {
        }

        @Override
        public void setOnCancelled(EventHandler<WorkerStateEvent> handler) {
            cancelledHandler = handler;
        }

        @Override
        public void setOnSucceeded(EventHandler<WorkerStateEvent> handler) {
            successHandler = handler;
        }

        @Override
        public void reset() {
        }

        @Override
        public void cancel() {
        }

        private class AsyncWorker implements Worker<Object> {

            private Object value;
            private String message;
            private State state;

            protected void setMessage(String message) {
                this.message = message;
            }

            protected void setState(State state) {
                this.state = state;
            }

            @Override
            public State getState() {
                return state;
            }

            @Override
            public ReadOnlyObjectProperty<State> stateProperty() {
                return null;
            }

            @Override
            public Object getValue() {
                return value;
            }

            public void setValue(Object object) {
                this.value = object;
            }

            @Override
            public ReadOnlyObjectProperty<Object> valueProperty() {
                return null;
            }

            @Override
            public Throwable getException() {
                return null;
            }

            @Override
            public ReadOnlyObjectProperty<Throwable> exceptionProperty() {
                return null;
            }

            @Override
            public double getWorkDone() {
                return 0;
            }

            @Override
            public ReadOnlyDoubleProperty workDoneProperty() {
                return null;
            }

            @Override
            public double getTotalWork() {
                return 0;
            }

            @Override
            public ReadOnlyDoubleProperty totalWorkProperty() {
                return null;
            }

            @Override
            public double getProgress() {
                return 0;
            }

            @Override
            public ReadOnlyDoubleProperty progressProperty() {
                return null;
            }

            @Override
            public boolean isRunning() {
                return false;
            }

            @Override
            public ReadOnlyBooleanProperty runningProperty() {
                return null;
            }

            @Override
            public String getMessage() {
                return message;
            }

            @Override
            public ReadOnlyStringProperty messageProperty() {
                return null;
            }

            @Override
            public String getTitle() {
                return null;
            }

            @Override
            public ReadOnlyStringProperty titleProperty() {
                return null;
            }

            @Override
            public boolean cancel() {
                return false;
            }
        }
    }

}