org.polymap.p4.data.importer.ImportPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.polymap.p4.data.importer.ImportPanel.java

Source

/* 
 * Copyright (C) 2015, the @authors. All rights reserved.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3.0 of
 * the License, or (at your option) any later version.
 *
 * This software 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
 * Lesser General Public License for more details.
 */
package org.polymap.p4.data.importer;

import static org.apache.commons.io.FileUtils.byteCountToDisplaySize;
import static org.polymap.core.runtime.UIThreadExecutor.async;
import static org.polymap.core.runtime.event.TypeEventFilter.ifType;
import static org.polymap.core.ui.FormDataFactory.on;
import static org.polymap.rhei.batik.app.SvgImageRegistryHelper.WHITE24;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.geotools.feature.FeatureCollection;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.eclipse.swt.SWT;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.viewers.StructuredSelection;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Status;
import org.eclipse.rap.rwt.RWT;
import org.eclipse.rap.rwt.client.ClientFile;
import org.eclipse.rap.rwt.client.service.ClientFileUploader;
import org.eclipse.rap.rwt.dnd.ClientFileTransfer;

import org.polymap.core.operation.DefaultOperation;
import org.polymap.core.operation.OperationSupport;
import org.polymap.core.runtime.Timer;
import org.polymap.core.runtime.event.EventHandler;
import org.polymap.core.runtime.event.EventManager;
import org.polymap.core.runtime.i18n.IMessages;
import org.polymap.core.ui.FormLayoutFactory;
import org.polymap.core.ui.SelectionAdapter;
import org.polymap.core.ui.StatusDispatcher;
import org.polymap.core.ui.UIUtils;

import org.polymap.rhei.batik.Context;
import org.polymap.rhei.batik.PanelIdentifier;
import org.polymap.rhei.batik.app.SvgImageRegistryHelper;
import org.polymap.rhei.batik.toolkit.IPanelSection;
import org.polymap.rhei.batik.toolkit.SimpleDialog;
import org.polymap.rhei.batik.toolkit.Snackbar.Appearance;
import org.polymap.rhei.batik.toolkit.md.MdListViewer;
import org.polymap.rhei.batik.toolkit.md.MdToolkit;
import org.polymap.rhei.batik.toolkit.md.TreeExpandStateDecorator;

import org.polymap.p4.P4Panel;
import org.polymap.p4.P4Plugin;
import org.polymap.p4.catalog.CatalogPanel;
import org.polymap.p4.data.importer.ImportsLabelProvider.Type;
import org.polymap.p4.data.importer.features.ImportFeaturesOperation;
import org.polymap.p4.layer.LayersCatalogsPanel;
import org.polymap.p4.layer.LayersPanel;
import org.polymap.p4.map.ProjectMapPanel;
import org.polymap.rap.updownload.upload.IUploadHandler;
import org.polymap.rap.updownload.upload.Upload;
import org.polymap.rap.updownload.upload.UploadService;

/**
 * 
 *
 * @author <a href="http://www.polymap.de">Falko Brutigam</a>
 */
public class ImportPanel extends P4Panel implements IUploadHandler {

    private static final Log log = LogFactory.getLog(ImportPanel.class);

    public static final PanelIdentifier ID = PanelIdentifier.parse("dataimport");

    private static final IMessages i18n = Messages.forPrefix("ImportPanel");

    // instance *******************************************

    private Context<ImporterContext> nextContext;

    private ImporterContext context;

    private boolean rootImportPanel;

    private MdListViewer importsList;

    private IPanelSection resultSection;

    private File tempDir = ImportTempDir.create();

    private Button fab;

    @Override
    public boolean wantsToBeShown() {
        return parentPanel()
                .filter(parent -> parent instanceof CatalogPanel || parent instanceof LayersPanel
                        || parent instanceof LayersCatalogsPanel || parent instanceof ProjectMapPanel)
                .map(parent -> {
                    site().title.set("");
                    site().tooltip.set(i18n.get("tooltip"));
                    site().icon.set(
                            ImporterPlugin.images().svgImage("database-plus.svg", P4Plugin.HEADER_ICON_CONFIG));
                    return true;
                }).orElse(false);
    }

    @Override
    public void init() {
        super.init();
        site().setSize(SIDE_PANEL_WIDTH, SIDE_PANEL_WIDTH2, Integer.MAX_VALUE);
        site().title.set(i18n.get("title"));
        context = nextContext.isPresent() ? nextContext.get() : new ImporterContext();

        // listen to ok (to execute) event from ImporterSite
        EventManager.instance().subscribe(this, ifType(ConfigChangeEvent.class,
                cce -> cce.propName.equals("ok") && cce.getSource() instanceof ImporterSite));
    }

    @Override
    public void dispose() {
        nextContext.set(context.importer() != null ? context : null);
        EventManager.instance().unsubscribe(this);
    }

    /**
     * After a prompt change has triggered verification in ImporterContext, the ok state
     * of the importer has been set -> select this importer in the list. This triggers
     * update of the FAB too. 
     */
    @EventHandler(display = true)
    protected void importerOkChanged(ConfigChangeEvent ev) {
        importsList.setSelection(new StructuredSelection(((ImporterSite) ev.getSource()).context()), true);
    }

    @Override
    public void createContents(Composite parent) {
        // margin left/right: shadow
        parent.setLayout(FormLayoutFactory.defaults().spacing(5).margins(3, 8).create());
        MdToolkit tk = (MdToolkit) site().toolkit();

        // upload button
        Upload upload = null;
        if (!nextContext.isPresent()) {
            upload = tk.adapt(new Upload(parent, SWT.FLAT)); //, Upload.SHOW_PROGRESS ) );
            upload.setBackground(parent.getBackground());
            upload.setImage(ImporterPlugin.images().svgImage("upload.svg", SvgImageRegistryHelper.ACTION24));
            upload.setText(i18n.get("upload"));
            upload.setToolTipText(i18n.get("uploadTooltip"));
            upload.setHandler(this);
            upload.moveAbove(null);

            DropTarget labelDropTarget = new DropTarget(upload, DND.DROP_MOVE);
            labelDropTarget.setTransfer(new Transfer[] { ClientFileTransfer.getInstance() });
            labelDropTarget.addDropListener(createDropTargetAdapter());
        }

        // imports and prompts
        importsList = tk.createListViewer(parent, SWT.VIRTUAL, SWT.SINGLE | SWT.FULL_SELECTION);
        importsList.setContentProvider(new ImportsContentProvider());
        importsList.firstLineLabelProvider
                .set(new TreeExpandStateDecorator(importsList, new ImportsLabelProvider(Type.Summary)));
        importsList.secondLineLabelProvider.set(new ImportsLabelProvider(Type.Description));
        importsList.iconProvider.set(new ImportsLabelProvider(Type.Icon));
        importsList.firstSecondaryActionProvider.set(new ImportsLabelProvider(Type.StatusIcon));

        // selection listener recognizes events from UI *and* from ImportsContentProvider
        importsList.addSelectionChangedListener(ev -> {
            SelectionAdapter.on(ev.getSelection()).forEach(elm -> {
                //
                if (elm instanceof ImporterContext) {
                    createResultViewer((ImporterContext) elm);
                    updateFab((ImporterContext) elm);

                    importsList.expandToLevel(elm, 1);
                }
                //
                else if (elm instanceof ImporterPrompt) {
                    createPromptViewer((ImporterPrompt) elm);
                }
            });
        });

        // ImportsContentProvider selects every ImportContext when it is loaded;
        // this triggers expansion and resultViewer (see above)
        importsList.setInput(context);

        // result viewer
        resultSection = tk.createPanelSection(parent, i18n.get("previewTitle"), SWT.BORDER);
        if (!nextContext.isPresent()) {
            tk.createFlowText(resultSection.getBody(), i18n.get("previewMsg"));
        } else {
            tk.createFlowText(resultSection.getBody(), i18n.get("previewNoData"));
        }

        // layout
        if (upload != null) {
            on(upload).fill().bottom(0, 40);
        }
        // width/height specifies that we want scrollbars in the contets
        on(importsList.getControl()).fill().top(0, 45).bottom(50, -10).width(100).height(100);
        on(resultSection.getControl()).fill().top(50, 5).height(100).width(100);
    }

    protected void createResultViewer(@SuppressWarnings("hiding") ImporterContext context) {
        resultSection.setTitle(i18n.get("previewTitle"));
        context.updateResultViewer(resultSection.getBody(), site().toolkit());
    }

    protected void updateFab(@SuppressWarnings("hiding") ImporterContext context) {
        if (fab != null) {
            fab.dispose();
        }

        if (context.site().ok.get()) {
            MdToolkit tk = (MdToolkit) site().toolkit();
            if (context.site().terminal.get()) {
                fab = tk.createFab(SWT.RIGHT);
                fab.setToolTipText(i18n.get("fabTooltip"));
            } else {
                fab = tk.createFab(null, ImporterPlugin.images().svgImage("arrow-right.svg", WHITE24), SWT.RIGHT);
                fab.setToolTipText(i18n.get("forwardTooltip"));
            }
            fab.getParent().layout();

            fab.addSelectionListener(new org.eclipse.swt.events.SelectionAdapter() {
                @Override
                public void widgetSelected(SelectionEvent ev) {
                    try {
                        if (context.site().terminal.get()) {
                            executeTerminalContext(context);
                        } else {
                            executeNonTerminalContext(context);
                        }
                    } catch (Exception e) {
                        StatusDispatcher.handleError("Importer did not execute successfully.", e);
                    }
                }
            });
        }
    }

    /**
     * Executes the given context and open next panel.
     */
    protected void executeNonTerminalContext(@SuppressWarnings("hiding") ImporterContext context) throws Exception {
        context.execute(new NullProgressMonitor());

        nextContext.set(context);
        getContext().openPanel(site().path(), ImportPanel.ID);
    }

    /**
     * 
     */
    protected void executeTerminalContext(@SuppressWarnings("hiding") ImporterContext context) throws Exception {
        // execute
        Map<Class, Object> contextOut = new HashMap();
        DefaultOperation executeOp = new DefaultOperation("") {
            @Override
            protected IStatus doExecute(IProgressMonitor monitor, IAdaptable info) throws Exception {
                contextOut.putAll(context.execute(monitor));
                return Status.OK_STATUS;
            }
        };
        OperationSupport.instance().execute(executeOp, false, false);

        // copy features
        FeatureCollection features = (FeatureCollection) contextOut.get(FeatureCollection.class);
        if (features != null) {
            ImportFeaturesOperation op = new ImportFeaturesOperation(context, features);
            getContext().propagate(op);
            OperationSupport.instance().execute(op, false, false);
        }

        // close panel
        getContext().closePanel(site().path());
    }

    protected void createPromptViewer(ImporterPrompt prompt) {
        SimpleDialog dialog = site().toolkit().createSimpleDialog(prompt.summary.get());

        dialog.setContents(parent -> prompt.context().createPromptViewer(parent, prompt, site().toolkit()));

        dialog.addAction(new Action("OK") {
            public void run() {
                prompt.extendedUI.get().submit(prompt);
                dialog.close();
            }
        });
        dialog.addAction(new Action("CANCEL") {
            public void run() {
                dialog.close();
            }
        });
        dialog.open();
    }

    @Override
    public void uploadStarted(ClientFile clientFile, InputStream in) throws Exception {
        log.info(clientFile.getName() + " - " + clientFile.getType() + " - " + clientFile.getSize());

        uploadProgress(resultSection.getBody(), "Uploading ...");

        // upload file
        assert clientFile.getName() != null : "Null client file name is not supported yet.";
        File f = new File(tempDir, clientFile.getName());
        try (OutputStream out = new FileOutputStream(f)) {
            Timer timer = new Timer();
            byte[] buf = new byte[4096];
            AtomicLong count = new AtomicLong();
            for (int c = in.read(buf); c > -1; c = in.read(buf)) {
                out.write(buf, 0, c);
                count.addAndGet(c);

                if (timer.elapsedTime() > 2000) {
                    Composite parent = resultSection.getBody();
                    if (parent.isDisposed()) {
                        break; // stop uploading
                    } else {
                        uploadProgress(resultSection.getBody(),
                                "Uploading ..." + byteCountToDisplaySize(count.get()));
                        timer.start();
                    }
                }
            }
            uploadProgress(resultSection.getBody(), "Upload ...complete.");
        } catch (Exception e) {
            uploadProgress(resultSection.getBody(), "Upload ...failed.");
            async(() -> site().toolkit().createSnackbar(Appearance.FadeIn, "Unable to upload file."));
            return;
        }

        async(() -> {
            // fires event which triggers UI update in ImportsContentProvider
            context.addContextOut(f);
        });
    }

    private void uploadProgress(Composite parent, String msg) {
        parent.getDisplay().asyncExec(() -> {
            if (!parent.isDisposed()) {
                parent.setLayout(new FillLayout(SWT.VERTICAL));
                UIUtils.disposeChildren(parent);
                tk().createFlowText(parent, msg);
                parent.layout();
            }
        });
    }

    protected DropTargetAdapter createDropTargetAdapter() {
        return new DropTargetAdapter() {
            @Override
            public void drop(DropTargetEvent ev) {
                ClientFile[] clientFiles = (ClientFile[]) ev.data;
                Arrays.stream(clientFiles).forEach(clientFile -> {
                    log.info(clientFile.getName() + " - " + clientFile.getType() + " - " + clientFile.getSize());

                    String uploadUrl = UploadService.registerHandler(ImportPanel.this);

                    ClientFileUploader uploader = RWT.getClient().getService(ClientFileUploader.class);
                    uploader.submit(uploadUrl, new ClientFile[] { clientFile });
                });
            }
        };
    }

}