org.geogig.geoserver.web.repository.DirectoryChooser.java Source code

Java tutorial

Introduction

Here is the source code for org.geogig.geoserver.web.repository.DirectoryChooser.java

Source

/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
 * This code is licensed under the GPL 2.0 license, available at the root
 * application directory.
 */
package org.geogig.geoserver.web.repository;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import javax.swing.filechooser.FileSystemView;

import org.apache.commons.io.FilenameUtils;
import org.apache.wicket.AttributeModifier;
import org.apache.wicket.MetaDataKey;
import org.apache.wicket.ajax.AjaxRequestTarget;
import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
import org.apache.wicket.ajax.markup.html.AjaxFallbackLink;
import org.apache.wicket.ajax.markup.html.AjaxLink;
import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.ChoiceRenderer;
import org.apache.wicket.markup.html.form.DropDownChoice;
import org.apache.wicket.markup.html.image.Image;
import org.apache.wicket.markup.html.panel.Panel;
import org.apache.wicket.markup.repeater.Item;
import org.apache.wicket.markup.repeater.data.DataView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.request.resource.PackageResourceReference;
import org.apache.wicket.util.convert.IConverter;
import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager;
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.platform.GeoServerResourceLoader;
import org.geoserver.web.GeoServerBasePage;
import org.geoserver.web.wicket.ParamResourceModel;
import org.geoserver.web.wicket.browser.FileBreadcrumbs;
import org.geoserver.web.wicket.browser.FileProvider;
import org.geoserver.web.wicket.browser.GeoServerFileChooser;

import com.google.common.collect.Lists;

/**
 * A geogig directory file chooser compoenent
 * <p>
 * Adapted from {@link GeoServerFileChooser}
 */
public class DirectoryChooser extends Panel {

    private static final long serialVersionUID = -5587014542924822012L;

    private final IModel<DirectoryFilter> fileFilter = new Model<>(new DirectoryFilter());

    static File USER_HOME = null;
    static {
        // try to safely determine the user home location
        try {
            File hf = null;
            String home = System.getProperty("user.home");
            if (home != null) {
                hf = new File(home);
            }
            if (hf != null && hf.exists()) {
                USER_HOME = hf;
            }
        } catch (Throwable t) {
            // that's ok, we might not be able to get the user home
        }
    }

    private static final MetaDataKey<File> LAST_VISITED_DIRECTORY = new MetaDataKey<File>() {

        private static final long serialVersionUID = 1L;
    };

    private FileBreadcrumbs breadcrumbs;

    private DirectoryDataView directoryListingTable;

    private final IModel<File> directory;

    private final boolean makeRepositoriesSelectable;

    private AjaxLink<?> accepdDirectoryLink;

    public DirectoryChooser(String contentId, IModel<File> directory) {
        this(contentId, directory, true);
    }

    public DirectoryChooser(final String contentId, IModel<File> initialDirectory,
            final boolean makeRepositoriesSelectable) {
        super(contentId, initialDirectory);
        getSession().bind();// so we can store the last visited directory as a session object
        this.makeRepositoriesSelectable = makeRepositoriesSelectable;

        if (initialDirectory.getObject() == null) {
            File lastUsed = getSession().getMetaData(LAST_VISITED_DIRECTORY);
            initialDirectory.setObject(lastUsed);
        }

        // build the roots
        ArrayList<File> roots = Lists.newArrayList(File.listRoots());
        Collections.sort(roots);

        // TODO: find a better way to deal with the data dir
        GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);
        File dataDirectory = loader.getBaseDirectory();

        roots.add(0, dataDirectory);

        // add the home directory as well if it was possible to determine it at all
        if (USER_HOME != null) {
            roots.add(1, USER_HOME);
        }

        // find under which root the selection should be placed
        File selection = initialDirectory.getObject();

        // first check if the file is a relative reference into the data dir
        if (selection != null) {
            File relativeToDataDir = loader.url(selection.getPath());
            if (relativeToDataDir != null) {
                selection = relativeToDataDir;
            }
        }

        // select the proper root
        File selectionRoot = null;
        if (selection != null && selection.exists()) {
            for (File root : roots) {
                if (isSubfile(root, selection.getAbsoluteFile())) {
                    selectionRoot = root;
                    break;
                }
            }

            // if the file is not part of the known search paths, give up
            // and switch back to the data directory
            if (selectionRoot == null) {
                selectionRoot = dataDirectory;
                initialDirectory = new Model<>(selectionRoot);
            } else {
                if (!selection.isDirectory()) {
                    initialDirectory = new Model<>(selection.getParentFile());
                } else {
                    initialDirectory = new Model<>(selection);
                }
            }
        } else {
            selectionRoot = dataDirectory;
            initialDirectory = new Model<>(selectionRoot);
        }
        this.directory = initialDirectory;
        setDefaultModel(initialDirectory);

        // the root chooser
        final DropDownChoice<File> choice = new DropDownChoice<>("roots", new Model<>(selectionRoot),
                new Model<ArrayList<File>>(roots), new FileRootsRenderer());
        choice.add(new AjaxFormComponentUpdatingBehavior("change") {
            private static final long serialVersionUID = -1113141016446727615L;

            @Override
            protected void onUpdate(AjaxRequestTarget target) {
                File selection = (File) choice.getModelObject();
                breadcrumbs.setRootFile(selection);
                updateFileBrowser(selection, target);
            }

        });
        choice.setOutputMarkupId(true);
        add(choice);

        // the breadcrumbs
        breadcrumbs = new FileBreadcrumbs("breadcrumbs", new Model<>(selectionRoot), initialDirectory) {
            private static final long serialVersionUID = 3637173832581301482L;

            @Override
            protected void pathItemClicked(File file, AjaxRequestTarget target) {
                updateFileBrowser(file, target);
            }

        };
        breadcrumbs.setOutputMarkupId(true);
        add(breadcrumbs);

        // the file tables
        directoryListingTable = new DirectoryDataView("fileTable", new FileProvider(initialDirectory),
                this.makeRepositoriesSelectable) {

            private static final long serialVersionUID = -1559299096797421815L;

            @Override
            protected void linkNameClicked(File file, AjaxRequestTarget target) {
                updateFileBrowser(file, target);
            }

        };
        directoryListingTable.setOutputMarkupId(true);
        directoryListingTable.setFileFilter(fileFilter);
        add(directoryListingTable);

        accepdDirectoryLink = new AjaxLink<File>("ok", this.directory) {
            private static final long serialVersionUID = 1L;

            @Override
            public void onClick(AjaxRequestTarget target) {
                // must of been set by #directoryClicked()
                File dir = getModelObject();
                getSession().setMetaData(LAST_VISITED_DIRECTORY, dir);
                directorySelected(dir, target);
            }
        };
        add(accepdDirectoryLink);
        accepdDirectoryLink.setVisible(!this.makeRepositoriesSelectable);
    }

    void updateFileBrowser(File file, AjaxRequestTarget target) {
        if (RepositoryManager.isGeogigDirectory(file)) {
            geogigDirectoryClicked(file, target);
        } else {
            getSession().setMetaData(LAST_VISITED_DIRECTORY, file);
            directoryClicked(file, target);
        }
    }

    /**
     * Called when a file name is clicked. By default it does nothing
     * @param file
     * @param target
     */
    protected void geogigDirectoryClicked(File file, AjaxRequestTarget target) {
        // do nothing, subclasses will override
    }

    /**
     * Action undertaken as a directory is clicked. Default behavior is to drill down into the
     * directory.
     * 
     * @param file
     * @param target
     */
    protected void directoryClicked(File file, AjaxRequestTarget target) {
        // explicitly change the root model, inform the other components the model has changed
        DirectoryChooser.this.directory.setObject(file);
        directoryListingTable.setDirectory(new Model<>(file));
        breadcrumbs.setSelection(file);

        target.add(directoryListingTable);
        target.add(breadcrumbs);
    }

    protected void directorySelected(File file, AjaxRequestTarget target) {
        // to be overriden
    }

    private boolean isSubfile(File root, File selection) {
        if (selection == null || "".equals(selection.getPath()))
            return false;
        if (selection.equals(root))
            return true;

        return isSubfile(root, selection.getParentFile());
    }

    /**
     * Set the file table fixed height. Set it to null if you don't want fixed height with overflow,
     * and to a valid CSS measure if you want it instead. Default value is "25em"
     * 
     * @param height
     */
    public void setFileTableHeight(String height) {
        directoryListingTable.setTableHeight(height);
    }

    class FileRootsRenderer extends ChoiceRenderer<File> {
        private static final long serialVersionUID = -5804668199121599078L;

        @Override
        public Object getDisplayValue(File f) {
            if (f == USER_HOME) {
                return new ParamResourceModel("userHome", DirectoryChooser.this).getString();
            } else {
                GeoServerResourceLoader loader = GeoServerExtensions.bean(GeoServerResourceLoader.class);

                if (f.equals(loader.getBaseDirectory())) {
                    return new ParamResourceModel("dataDirectory", DirectoryChooser.this).getString();
                }
            }

            try {
                final String displayName = FileSystemView.getFileSystemView().getSystemDisplayName(f);
                if (displayName != null && displayName.length() > 0) {
                    return displayName;
                }
                return FilenameUtils.getPrefix(f.getAbsolutePath());
            } catch (Exception e) {
                // on windows we can get the occasional NPE due to
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6973685
            }
            return f.getName();
        }

        @Override
        public String getIdValue(File f, int count) {
            return "" + count;
        }
    }

    private static final class DirectoryFilter implements FileFilter, Serializable {
        private static final long serialVersionUID = -2280505390702552L;

        @Override
        public boolean accept(File file) {
            return file.isDirectory();
        }
    }

    static abstract class DirectoryDataView extends Panel {

        private static final PackageResourceReference FOLDER = new PackageResourceReference(GeoServerBasePage.class,
                "img/icons/silk/folder.png");

        private static final PackageResourceReference GEOGIG_FOLDER = new PackageResourceReference(
                DirectoryDataView.class, "../geogig_16x16_babyblue.png");

        private static final long serialVersionUID = -2932107412054607607L;

        private final boolean allowSelectingRepositories;

        private final IConverter FILE_NAME_CONVERTER = new StringConverter() {

            private static final long serialVersionUID = 2050812486536366790L;

            @Override
            public String convertToString(Object value, Locale locale) {
                File file = (File) value;
                if (file.isDirectory()) {
                    if (RepositoryManager.isGeogigDirectory(file)) {
                        return file.getName();
                    }
                    return file.getName() + "/";
                } else {
                    return file.getName();
                }
            }

        };

        private static final IConverter FILE_LASTMODIFIED_CONVERTER = new StringConverter() {

            private static final long serialVersionUID = 7862772890388011374L;

            @Override
            public String convertToString(Object value, Locale locale) {
                File file = (File) value;
                long lastModified = file.lastModified();
                if (lastModified == 0L)
                    return null;
                else {
                    return DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT)
                            .format(new Date(file.lastModified()));
                }
            }

        };

        private SortableDataProvider<File, String> provider;

        private final WebMarkupContainer fileContent;

        private String tableHeight = "25em";

        @SuppressWarnings("unchecked")
        public DirectoryDataView(final String id, final FileProvider fileProvider,
                final boolean allowSelectingRepositories) {
            super(id);
            this.provider = fileProvider;
            this.allowSelectingRepositories = allowSelectingRepositories;

            final WebMarkupContainer table = new WebMarkupContainer("fileTable");
            table.setOutputMarkupId(true);
            add(table);

            List<RepositoryInfo> all = RepositoryManager.get().getAll();

            // maps canonical files to configured file to identify duplicates due to symlinks
            final Map<File, File> existingPaths = new HashMap<>();
            for (RepositoryInfo info : all) {
                final URI uri = info.getLocation();
                if ("file".equals(uri.getScheme())) {
                    File configured = new File(uri);
                    File canonical;
                    try {
                        canonical = configured.getCanonicalFile();
                    } catch (IOException e) {
                        canonical = configured;
                    }
                    existingPaths.put(canonical, configured);
                }
            }

            DataView<File> fileTable = new DataView<File>("files", fileProvider) {
                private static final long serialVersionUID = 1345694542339080271L;

                @Override
                protected void populateItem(final Item<File> item) {
                    // odd/even alternate style
                    item.add(AttributeModifier.replace("class", item.getIndex() % 2 == 0 ? "even" : "odd"));

                    final File file = item.getModelObject();
                    final boolean isGeogigDirectory = RepositoryManager.isGeogigDirectory(file);
                    PackageResourceReference icon = isGeogigDirectory ? GEOGIG_FOLDER : FOLDER;
                    item.add(new Image("icon", icon));

                    // navigation/selection links
                    AjaxFallbackLink<File> link = new AjaxFallbackLink<File>("nameLink") {
                        private static final long serialVersionUID = -644973941443812893L;

                        @Override
                        public void onClick(AjaxRequestTarget target) {
                            linkNameClicked((File) item.getModelObject(), target);
                        }

                    };
                    Label nameLabel = new Label("name", item.getModel()) {

                        private static final long serialVersionUID = -4028081066393114129L;

                        @Override
                        public IConverter getConverter(Class type) {
                            return FILE_NAME_CONVERTER;
                        }
                    };
                    link.add(nameLabel);

                    final Map<File, File> existing = existingPaths;
                    File canonicalFile;
                    try {
                        canonicalFile = file.getCanonicalFile();
                    } catch (IOException e) {
                        canonicalFile = file;
                    }

                    final boolean alreadyImported = isGeogigDirectory && existing.containsKey(canonicalFile);
                    if (isGeogigDirectory) {
                        if (alreadyImported) {
                            link.setEnabled(false);
                            File dupicate = existing.get(canonicalFile);
                            nameLabel
                                    .add(AttributeModifier.replace("title",
                                            new ParamResourceModel("DirectoryChooser$DirectoryDataView.repoExists",
                                                    DirectoryDataView.this, dupicate.getAbsolutePath())
                                                            .getObject()));
                        } else {
                            link.setEnabled(DirectoryDataView.this.allowSelectingRepositories);
                        }
                    }

                    item.add(link);

                    // last modified and size labels
                    item.add(new Label("lastModified", item.getModel()) {

                        private static final long serialVersionUID = -4706544449170830483L;

                        @Override
                        public IConverter getConverter(Class type) {
                            return FILE_LASTMODIFIED_CONVERTER;
                        }
                    });
                }

            };

            fileContent = new WebMarkupContainer("fileContent") {

                private static final long serialVersionUID = -4197754944388542068L;

                @Override
                protected void onComponentTag(ComponentTag tag) {
                    if (tableHeight != null) {
                        tag.getAttributes().put("style", "overflow:auto; height:" + tableHeight);
                    }
                }
            };

            fileContent.add(fileTable);

            table.add(fileContent);
        }

        protected abstract void linkNameClicked(File file, AjaxRequestTarget target);

        private static abstract class StringConverter implements IConverter {

            private static final long serialVersionUID = -6464669942374870999L;

            @Override
            public Object convertToObject(String value, Locale locale) {
                throw new UnsupportedOperationException("This converter works only for strings");
            }
        }

        public SortableDataProvider<File, String> getProvider() {
            return provider;
        }

        public void setTableHeight(String tableHeight) {
            this.tableHeight = tableHeight;
        }

        public void setFileFilter(IModel<? extends FileFilter> fileFilter) {
            ((FileProvider) provider).setFileFilter(fileFilter);
        }

        public void setDirectory(Model<File> model) {
            ((FileProvider) provider).setDirectory(model);
        }
    }
}