org.orbisgis.geocatalog.impl.SourceListModel.java Source code

Java tutorial

Introduction

Here is the source code for org.orbisgis.geocatalog.impl.SourceListModel.java

Source

/**
 * OrbisGIS is a java GIS application dedicated to research in GIScience.
 * OrbisGIS is developed by the GIS group of the DECIDE team of the 
 * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>.
 *
 * The GIS group of the DECIDE team is located at :
 *
 * Laboratoire Lab-STICC  CNRS UMR 6285
 * Equipe DECIDE
 * UNIVERSIT DE BRETAGNE-SUD
 * Institut Universitaire de Technologie de Vannes
 * 8, Rue Montaigne - BP 561 56017 Vannes Cedex
 * 
 * OrbisGIS is distributed under GPL 3 license.
 *
 * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488)
 * Copyright (C) 2015-2016 CNRS (Lab-STICC UMR CNRS 6285)
 *
 * This file is part of OrbisGIS.
 *
 * OrbisGIS 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.
 *
 * OrbisGIS 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
 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>.
 *
 * For more information, please consult: <http://www.orbisgis.org/>
 * or contact directly:
 * info_at_ orbisgis.org
 */
package org.orbisgis.geocatalog.impl;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.sql.DataSource;
import javax.swing.*;

import org.h2gis.utilities.JDBCUtilities;
import org.orbisgis.corejdbc.DataManager;
import org.orbisgis.corejdbc.DatabaseProgressionListener;
import org.orbisgis.corejdbc.StateEvent;
import org.orbisgis.geocatalog.impl.filters.IFilter;
import org.orbisgis.geocatalog.impl.filters.TableSystemFilter;
import org.orbisgis.sif.common.ContainerItemProperties;
import org.h2gis.utilities.TableLocation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

import static org.apache.commons.collections.ComparatorUtils.NATURAL_COMPARATOR;

/**
 * Manage entries of GeoCatalog according to a database
 * SourceListModel is a swing component that update the content of the geocatalog
 * according to the SourceManager content and the filter loaded.
 */
public class SourceListModel extends AbstractListModel<ContainerItemProperties>
        implements DatabaseProgressionListener {
    private static final I18n I18N = I18nFactory.getI18n(SourceListModel.class);
    private static final Logger LOGGER = LoggerFactory.getLogger(SourceListModel.class);
    private static final long serialVersionUID = 1L;
    private static final String[] SHOWN_TABLE_TYPES = new String[] { "TABLE", "SYSTEM TABLE", "LINKED TABLE",
            "VIEW", "EXTERNAL" };
    /** Non filtered tables */
    private List<Map<IFilter.ATTRIBUTES, String>> allTables = new ArrayList<>();
    /** Filtered tables */
    private ContainerItemProperties[] sourceList = new ContainerItemProperties[0];/*!< Sources */
    private List<IFilter> filters = new ArrayList<>(); /*!< Active filters */
    private DefaultFilter defaultFilter = new DefaultFilter();
    private AtomicBoolean awaitingRefresh = new AtomicBoolean(
            false); /*!< If true a swing runnable
                    * is pending to refresh the content of SourceListModel*/
    private boolean updateWhileAwaitingRefresh = false;
    private DataSource dataSource;
    private CatalogComparator catalogComparator = new CatalogComparator();
    private boolean isH2;
    private Map<String, Integer> columnMap = new HashMap<>();

    /**
     * Read filters components and generate filter instances
     * @return A list of filters
     */
    public List<IFilter> getFilters() {
        return filters;
    }

    /**
     * Constructor
     * @param dataManager
     * @note Do not forget to call dispose()
     */
    public SourceListModel(DataManager dataManager) {
        this.dataSource = dataManager.getDataSource();
        try (Connection connection = dataSource.getConnection()) {
            isH2 = JDBCUtilities.isH2DataBase(connection.getMetaData());
        } catch (SQLException ex) {
            LOGGER.error(ex.getLocalizedMessage(), ex);
        }
        //Install database listeners
        dataManager.addDatabaseProgressionListener(this, StateEvent.DB_STATES.STATE_STATEMENT_END);
        //Call readDatabase when a SourceManager fire an event
        onDataManagerChange();
    }

    @Override
    public void progressionUpdate(StateEvent state) {
        if (state.isUpdateDatabaseStructure()) {
            onDataManagerChange();
        }
    }

    /**
     * Install listener(s) on SourceManager
     */
    public void setListeners() {
        // TODO, set a timer that hash table list
    }

    /**
     * The DataManager fire a DataSourceEvent
     * Swing will update the list later.
     * This method is called by the EventSource listener
     */
    public void onDataManagerChange() {
        //This is useless to invoke a refresh thread because
        //The content will be refresh is coming soon fired by another ReadDataManagerOnSwingThread
        if (!awaitingRefresh.getAndSet(true)) {
            ReadDataManagerOnSwingThread worker = new ReadDataManagerOnSwingThread(this);
            worker.execute();
        } else {
            updateWhileAwaitingRefresh = true;
        }
    }

    /**
     * Refresh the JList on the swing thread
     */
    private static class ReadDataManagerOnSwingThread extends SwingWorker<Boolean, Boolean> {
        private SourceListModel model;

        private ReadDataManagerOnSwingThread(SourceListModel model) {
            this.model = model;
        }

        @Override
        protected Boolean doInBackground() throws Exception {
            model.readDatabase();
            return true;
        }

        @Override
        protected void done() {
            //Refresh the JList on the swing thread
            model.doFilter();
            model.awaitingRefresh.set(false);
            // An update occurs during fetching tables
            if (model.updateWhileAwaitingRefresh) {
                model.updateWhileAwaitingRefresh = false;
                model.onDataManagerChange();
            }
        }
    }

    /**
     * TODO stop timers
     */
    public void dispose() {

    }

    /**
     * Find the icon corresponding to a table reference
     */
    private String getIconName(TableLocation location, Map<IFilter.ATTRIBUTES, String> attr) {
        if (attr.containsKey(IFilter.ATTRIBUTES.GEOMETRY_TYPE)) {
            return "geofile";
        }
        String tableType = attr.get(IFilter.ATTRIBUTES.TABLE_TYPE);
        if (tableType != null) {
            switch (tableType) {
            case "SYSTEM_TABLE":
                return "drive";
            case "LINKED TABLE":
                return "database";
            default:
                return "flatfile";
            }
        } else {
            return "flatfile";
        }
        //"remove";
        //"image";
        //"server_connect";
        // information_geo // Unknown
    }

    private static String addQuotesIfNecessary(String tableLocationPart) {
        if (tableLocationPart.contains(".")) {
            return "\"" + tableLocationPart + "\"";
        } else {
            return tableLocationPart;
        }
    }

    protected void doFilter() {
        boolean checkForDefaultFilter = true;
        for (IFilter filter : filters) {
            if (filter instanceof TableSystemFilter) {
                checkForDefaultFilter = false;
            }
        }
        List<CatalogSourceItem> newModel = new LinkedList<>();
        for (Map<IFilter.ATTRIBUTES, String> tableAttr : allTables) {
            boolean accepts = true;
            TableLocation location = TableLocation.parse(tableAttr.get(IFilter.ATTRIBUTES.LOCATION), isH2);
            for (IFilter filter : filters) {
                if (!filter.accepts(location, tableAttr)) {
                    accepts = false;
                    break;
                }
            }
            if (accepts && (!checkForDefaultFilter || defaultFilter.accepts(location, tableAttr))) {
                newModel.add(new CatalogSourceItem(location.toString(isH2), tableAttr.get(IFilter.ATTRIBUTES.LABEL),
                        getIconName(location, tableAttr)));
            }
        }
        Collections.sort(newModel, catalogComparator);
        int oldLength = sourceList.length;
        sourceList = new ContainerItemProperties[0];
        fireIntervalRemoved(this, 0, oldLength);
        sourceList = newModel.toArray(new ContainerItemProperties[newModel.size()]);
        fireIntervalAdded(this, 0, this.sourceList.length);
    }

    /**
     * Read the table list in the database
     */
    protected void readDatabase() {
        List<Map<IFilter.ATTRIBUTES, String>> newTables = new ArrayList<>(allTables.size());
        try (Connection connection = dataSource.getConnection()) {
            final String defaultCatalog = connection.getCatalog();
            String defaultSchema = "PUBLIC";
            try {
                if (connection.getSchema() != null) {
                    defaultSchema = connection.getSchema();
                }
            } catch (AbstractMethodError | Exception ex) {
                // Driver has been compiled with JAVA 6, or is not implemented
            }
            catalogComparator.setDefaultSchema(defaultSchema);
            // Fetch Geometry tables
            Map<String, String> tableGeometry = new HashMap<>();
            try (Statement st = connection.createStatement();
                    ResultSet rs = st.executeQuery("SELECT * FROM " + defaultSchema + ".geometry_columns")) {
                while (rs.next()) {
                    tableGeometry.put(new TableLocation(rs.getString("F_TABLE_CATALOG"),
                            rs.getString("F_TABLE_SCHEMA"), rs.getString("F_TABLE_NAME")).toString(),
                            rs.getString("TYPE"));
                }
            } catch (SQLException ex) {
                LOGGER.warn(I18N.tr("Geometry columns information of tables are not available"), ex);
            }
            // Fetch all tables
            try (ResultSet rs = connection.getMetaData().getTables(null, null, null, SHOWN_TABLE_TYPES)) {
                while (rs.next()) {
                    Map<IFilter.ATTRIBUTES, String> tableAttr = new HashMap<>(IFilter.ATTRIBUTES.values().length);
                    TableLocation location = new TableLocation(rs);
                    if (location.getCatalog().isEmpty()) {
                        // PostGIS return empty catalog on metadata
                        location = new TableLocation(defaultCatalog, location.getSchema(), location.getTable());
                    }
                    // Make Label
                    StringBuilder label = new StringBuilder(addQuotesIfNecessary(location.getTable()));
                    if (!location.getSchema().isEmpty() && !location.getSchema().equalsIgnoreCase(defaultSchema)) {
                        label.insert(0, ".");
                        label.insert(0, addQuotesIfNecessary(location.getSchema()));
                    }
                    if (!location.getCatalog().isEmpty()
                            && !location.getCatalog().equalsIgnoreCase(defaultCatalog)) {
                        label.insert(0, ".");
                        label.insert(0, addQuotesIfNecessary(location.getCatalog()));
                    }
                    // Shortcut location for H2 database
                    TableLocation shortLocation;
                    if (isH2) {
                        shortLocation = new TableLocation("",
                                location.getSchema().equals(defaultSchema) ? "" : location.getSchema(),
                                location.getTable());
                    } else {
                        shortLocation = new TableLocation(
                                location.getCatalog().equalsIgnoreCase(defaultCatalog) ? "" : location.getCatalog(),
                                location.getCatalog().equalsIgnoreCase(defaultCatalog)
                                        && location.getSchema().equalsIgnoreCase(defaultSchema) ? ""
                                                : location.getSchema(),
                                location.getTable());
                    }
                    tableAttr.put(IFilter.ATTRIBUTES.LOCATION, shortLocation.toString(isH2));
                    tableAttr.put(IFilter.ATTRIBUTES.LABEL, label.toString());
                    for (IFilter.ATTRIBUTES attribute : IFilter.ATTRIBUTES.values()) {
                        putAttribute(tableAttr, attribute, rs);
                    }
                    String type = tableGeometry.get(location.toString());
                    if (type != null) {
                        tableAttr.put(IFilter.ATTRIBUTES.GEOMETRY_TYPE, type);
                    }
                    newTables.add(tableAttr);
                }
            }
            allTables = newTables;
        } catch (SQLException ex) {
            LOGGER.error(I18N.tr("Cannot read the table list"), ex);
        }
    }

    private void putAttribute(Map<IFilter.ATTRIBUTES, String> tableAttr, IFilter.ATTRIBUTES attribute,
            ResultSet rs) {
        try {
            String columnName = attribute.toString().toLowerCase();
            Integer columnId = columnMap.get(columnName);
            if (columnId == null) {
                ResultSetMetaData meta = rs.getMetaData();
                columnMap.put(columnName, 0);
                for (int i = 1; i <= meta.getColumnCount(); i++) {
                    if (columnName.equals(meta.getCatalogName(i).toLowerCase())) {
                        columnMap.put(columnName, i);
                        columnId = i;
                        break;
                    }
                }
            }
            if (columnId != null && columnId > 0) {
                tableAttr.put(attribute, rs.getString(columnId));
            }
        } catch (SQLException ex) {
            // Ignore
        }
    }

    /**
     *
     * @param index The item index @see getSize()
     * @return The item
     */
    @Override
    public ContainerItemProperties getElementAt(int index) {
        return sourceList[index];
    }

    /**
     *
     * @return The number of source shown
     */
    @Override
    public int getSize() {
        return sourceList.length;
    }

    /**
     * Set the filter and refresh the Source list
     * according to the new filter
     * @param filters A collection of filters
     */
    public void setFilters(List<IFilter> filters) {
        this.filters = filters;
        doFilter();
    }

    /**
     * Remove all filters and refresh the Source list
     */
    public void clearFilters() {
        this.filters.clear();
        doFilter();
    }

    /**
     * This filter is always applied, to hide system tables
     */
    private static final class DefaultFilter implements IFilter {
        private TableSystemFilter filter = new TableSystemFilter();

        @Override
        public boolean accepts(TableLocation table, Map<ATTRIBUTES, String> tableProperties) {
            return !filter.accepts(table, tableProperties);
        }
    }

    private static class CatalogComparator implements Comparator<CatalogSourceItem> {
        private String defaultSchema = "PUBLIC";

        @Override
        public int compare(CatalogSourceItem left, CatalogSourceItem right) {
            TableLocation locationLeft = TableLocation.parse(left.getKey());
            TableLocation locationRight = TableLocation.parse(right.getKey());
            int tmpCompare = 0;
            // Sort by catalog
            tmpCompare = NATURAL_COMPARATOR.compare(locationLeft.getCatalog(), locationRight.getCatalog());
            if (tmpCompare != 0) {
                return tmpCompare;
            }
            // If catalog the same, sort by schema (default first)
            tmpCompare = NATURAL_COMPARATOR.compare(locationLeft.getSchema(), locationRight.getSchema());
            if (tmpCompare != 0) {
                if (locationLeft.getSchema().equals(defaultSchema)) {
                    return -1;
                } else if (locationRight.getSchema().equalsIgnoreCase(defaultSchema)) {
                    return 1;
                } else {
                    return tmpCompare;
                }
            }
            // if schema the same, sort by table
            return NATURAL_COMPARATOR.compare(locationLeft.getTable(), locationRight.getTable());
        }

        public void setDefaultSchema(String defaultSchema) {
            this.defaultSchema = defaultSchema;
        }
    }
}