Package org.geotools.arcsde.data

Source Code of org.geotools.arcsde.data.FeatureTypeInfoCache

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2009, Open Source Geospatial Foundation (OSGeo)
*
*    This library 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;
*    version 2.1 of the License.
*
*    This library 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.geotools.arcsde.data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import net.sf.jsqlparser.statement.select.PlainSelect;

import org.geotools.arcsde.session.Command;
import org.geotools.arcsde.session.ISession;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.arcsde.session.UnavailableConnectionException;
import org.geotools.data.DataSourceException;
import org.geotools.feature.NameImpl;
import org.geotools.util.logging.Logging;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;

import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeDefs;
import com.esri.sde.sdk.client.SeError;
import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeLayer;
import com.esri.sde.sdk.client.SeRegistration;
import com.esri.sde.sdk.client.SeTable;

/**
* Maintains a cache of {@link FeatureTypeInfo} objects for fast retrieval of ArcSDE vector layer
* information and its corresponding geotools {@link FeatureType}.
* <p>
* {@link SeLayer} objects are not cached, they hold a reference to its connection and hence may
* only be used inside the connection's context. Instead, a set of layer names is kept and the set
* of actual {@link FeatureTypeInfo}s is lazily loaded on demand.
* </p>
* <p>
* This class may set up a background process to periodically update the list of available layer
* names in the server and clear the feature type cache. See the constructor's javadoc for more
* info.
* </p>
*
* @author Gabriel Roldan
* @version $Id$
* @since 2.5.6
* @source $URL:
*         http://svn.osgeo.org/geotools/trunk/modules/plugin/arcsde/datastore/src/main/java/org
*         /geotools/arcsde/data/FeatureTypeInfoCache.java $
*/
final class FeatureTypeInfoCache {

    private static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde.data");

    /**
     * ArcSDE registered layers definitions
     */
    private final Map<String, FeatureTypeInfo> featureTypeInfos;

    /**
     * In process view definitions. This map is populated through
     * {@link #registerView(String, PlainSelect)}
     */
    private final Map<String, FeatureTypeInfo> inProcessFeatureTypeInfos;

    private final ISessionPool sessionPool;

    /**
     * list of available featureclasses in the database. Does not contain in-process view type
     * names. SeLayer objects are not cached because they hold a reference to its SeConnection and
     * hence need to be used only inside its connection context.
     */
    private final Set<String> availableLayerNames;

    /**
     * Namespace URI to construct FeatureTypes and AttributeTypes with
     */
    private final String namespace;

    /**
     * Scheduler for cache updating.
     */
    private ScheduledExecutorService cacheUpdateScheduler;

    /**
     * Lock for protecting featureTypeInfos cache.
     */
    private final ReentrantReadWriteLock cacheLock;

    private final boolean allowNonSpatialTables;

    private final long cacheUpdateFreqSecs;

    /**
     * Creates a FeatureTypeInfoCache
     * <p>
     * The provided {@link ISessionPool} is used to grab an {@link ISession} when the list of
     * available layers needs to be updated. This update happens at this class' construction time
     * and, optionally, every {@code cacheUpdateFreqSecs} seconds.
     * </p>
     *
     * @param sessionPool
     * @param namespace
     *            the namespace {@link FeatureType}s are created with, may be {@code null}
     * @param cacheUpdateFreqSecs
     *            layer name cache update frequency, in seconds. {@code <= 0} means do never update.
     * @param allowNonSpatialTables
     *            whether non spatial table names are requested
     * @throws IOException
     */
    public FeatureTypeInfoCache(final ISessionPool sessionPool, final String namespace,
            final int cacheUpdateFreqSecs, boolean allowNonSpatialTables) throws IOException {

        availableLayerNames = new TreeSet<String>();
        featureTypeInfos = new HashMap<String, FeatureTypeInfo>();
        inProcessFeatureTypeInfos = new HashMap<String, FeatureTypeInfo>();
        this.sessionPool = sessionPool;
        this.allowNonSpatialTables = allowNonSpatialTables;
        this.namespace = namespace;
        this.cacheLock = new ReentrantReadWriteLock();
        this.cacheUpdateFreqSecs = cacheUpdateFreqSecs;

        reset();
    }

    public void reset() {
        dispose();

        CacheUpdater cacheUpdater = new CacheUpdater();
        // run now, populate table name cache and then register for periodic running
        cacheUpdater.run();
        if (cacheUpdateFreqSecs > 0) {
            cacheUpdateScheduler = Executors.newScheduledThreadPool(1);
            LOGGER.info("Scheduling the layer name cache to be updated every "
                    + this.cacheUpdateFreqSecs + " seconds.");
            cacheUpdateScheduler.scheduleWithFixedDelay(cacheUpdater, this.cacheUpdateFreqSecs,
                    this.cacheUpdateFreqSecs, TimeUnit.SECONDS);
        } else {
            cacheUpdateScheduler = null;
        }
    }

    public boolean isAllowNonSpatialTables() {
        return allowNonSpatialTables;
    }

    public void dispose() {
        if (cacheUpdateScheduler != null) {
            LOGGER.info("Shutting down cache update scheduler");
            cacheUpdateScheduler.shutdownNow();
        }
    }

    public void addInprocessViewInfo(final FeatureTypeInfo typeInfo) {
        inProcessFeatureTypeInfos.put(typeInfo.getFeatureTypeName(), typeInfo);
    }

    public String getNamesapceURI() {
        return namespace;
    }

    public List<String> getTypeNames() {
        cacheLock.readLock().lock();

        List<String> layerNames;
        try {
            layerNames = new ArrayList<String>(availableLayerNames);
        } finally {
            cacheLock.readLock().unlock();
        }
        layerNames.addAll(this.inProcessFeatureTypeInfos.keySet());
        Collections.sort(layerNames);
        return layerNames;
    }

    public List<Name> getNames() {
        final List<String> typeNames = getTypeNames();
        List<Name> names = new ArrayList<Name>(typeNames.size());
        for (String typeName : typeNames) {
            NameImpl name = namespace == null ? new NameImpl(typeName) : new NameImpl(namespace,
                    typeName);
            names.add(name);
        }
        return names;
    }

    /**
     * Check inProcessFeatureTypeInfos and featureTypeInfos for the provided typeName, checking the
     * ArcSDE server as a last resort.
     *
     * @param typeName
     * @return
     */
    public FeatureTypeInfo getFeatureTypeInfo(final String typeName) throws IOException {
        FeatureTypeInfo typeInfo = getCachedTypeInfo(typeName);
        if (typeInfo != null) {
            return typeInfo;
        }

        ISession session;
        try {
            session = sessionPool.getSession(false);
        } catch (UnavailableConnectionException e) {
            throw new RuntimeException("Can't get type info for " + typeName
                    + ". Connection pool exhausted", e);
        }
        try {
            typeInfo = getFeatureTypeInfo(typeName, session);
        } finally {
            session.dispose();
        }
        return typeInfo;
    }

    /**
     * Used by feature reader and writer to get the schema information.
     * <p>
     * They are making use of this function because they already have their own Session to request
     * the ftInfo if needed.
     * </p>
     *
     * @param typeName
     * @param session
     * @return
     * @throws IOException
     */
    public FeatureTypeInfo getFeatureTypeInfo(final String typeName, final ISession session)
            throws IOException {

        FeatureTypeInfo typeInfo = getCachedTypeInfo(typeName);
        if (typeInfo != null) {
            return typeInfo;
        }

        cacheLock.writeLock().lock();
        // Recheck so it hasn't been done already.
        try {
            typeInfo = featureTypeInfos.get(typeName);
            if (typeInfo == null) {
                typeInfo = ArcSDEAdapter.fetchSchema(typeName, this.namespace, session);
                featureTypeInfos.put(typeName, typeInfo);
            }
        } finally {
            cacheLock.writeLock().unlock();
        }
        return typeInfo;
    }

    /**
     * @param typeName
     * @return the cached type info if there's one for typeName, {@code null} otherwise
     * @throws DataSourceException
     */
    private FeatureTypeInfo getCachedTypeInfo(final String typeName) throws DataSourceException {
        FeatureTypeInfo typeInfo = inProcessFeatureTypeInfos.get(typeName);
        if (typeInfo != null) {
            return typeInfo;
        }

        // Check if this is a known featureType
        cacheLock.readLock().lock();
        try {
            if (!availableLayerNames.contains(typeName)) {
                throw new DataSourceException(typeName + " does not exist");
            }
            typeInfo = featureTypeInfos.get(typeName);
        } finally {
            cacheLock.readLock().unlock();
        }

        return typeInfo;
    }

    private final class CacheUpdater implements Runnable {

        public void run() {
            LOGGER.finer("FeatureTypeCache background process running...");

            List<String> typeNames;
            try {
                typeNames = fetchRegistrations();
            } catch (Exception e) {
                LOGGER.log(Level.SEVERE, "Updating TypeNameCache failed.", e);
                return;
            }

            final Set<String> removed;
            {// just some logging..
                cacheLock.readLock().lock();
                Set<String> added = new TreeSet<String>(typeNames);
                added.removeAll(availableLayerNames);
                if (added.size() > 0) {
                    LOGGER.finest("FeatureTypeCache: added the following layers: " + added);
                }
                removed = new TreeSet<String>(availableLayerNames);
                removed.removeAll(typeNames);
                if (removed.size() > 0) {
                    LOGGER.finest("FeatureTypeCache: the following layers are no "
                            + "longer available: " + removed);
                }
                cacheLock.readLock().unlock();
            }
            cacheLock.writeLock().lock();

            availableLayerNames.clear();
            availableLayerNames.addAll(typeNames);

            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.finest("FeatureTypeCache: updated server layer list: " + typeNames);
            }

            // discard any removed feature type
            for (String typeName : removed) {
                LOGGER.fine("Removing FeatureTypeInfo for layer " + typeName
                        + " since it does no longer exist on the database");
                featureTypeInfos.remove(typeName);
            }

            LOGGER.finer("Finished updated type name cache");
            cacheLock.writeLock().unlock();
        }

        private List<String> fetchRegistrations() throws Exception {
            final List<String> typeNames;
            final ISession session = sessionPool.getSession(false);
            try {
                typeNames = session.issue(new FetchRegistrationsCommand(allowNonSpatialTables));
            } finally {
                session.dispose();
            }
            return typeNames;
        }
    }

    private static final class FetchRegistrationsCommand extends Command<List<String>> {

        private final boolean allowNonSpatialTables;

        public FetchRegistrationsCommand(boolean allowNonSpatialTables) {
            this.allowNonSpatialTables = allowNonSpatialTables;
        }

        @SuppressWarnings("unchecked")
        @Override
        public List<String> execute(ISession session, SeConnection connection) throws SeException,
                IOException {

            final Set<String> systemTables;
            {
                final String sdeDbaName = connection.getSdeDbaName().toUpperCase();
                final String dbName = connection.getDatabaseName();
                final String prefix = (dbName != null && dbName.length() > 0 ? (dbName
                        .toUpperCase() + ".") : "") + sdeDbaName + ".";
                systemTables = new HashSet<String>(Arrays.asList(//
                        prefix + "GDB_ITEMRELATIONSHIPS", //
                        prefix + "GDB_ITEMRELATIONSHIPTYPES", //
                        prefix + "GDB_ITEMS", //
                        prefix + "GDB_ITEMTYPES", //
                        prefix + "GDB_REPLICALOG" //
                ));
            }
            /*
             * Note we could do almost the same by calling
             * connection.getRegisteredTables():Vector<SeRegistration> but SeRegistration does not
             * have a getQualifiedName() method so I fear we can loose ability to serve feature
             * types from different users. So first getting the list of all the tables with select
             * privilege and then checking if it's registered...
             */
            final List<SeTable> registeredTables = connection.getTables(SeDefs.SE_SELECT_PRIVILEGE);

            /*
             * Get the list of raster tables so they're ignored as feature types
             */
            final List<String> rasterColumns = session.getRasterColumns();

            final List<String> typeNames = new ArrayList<String>(registeredTables.size());
            for (SeTable table : registeredTables) {
                final String tableName = table.getQualifiedName().toUpperCase();
                if (rasterColumns.contains(tableName)) {
                    continue;
                }
                if (systemTables.contains(tableName)) {
                    continue;
                }
                SeRegistration reg;
                try {
                    reg = new SeRegistration(connection, tableName);
                    // do not call getInfo or it'll fail with tables owned by other user than the
                    // connection one
                    // reg.getInfo();
                } catch (SeException e) {
                    if (e.getSeError().getSdeError() == SeError.SE_TABLE_NOREGISTERED) {
                        LOGGER.finest("Ignoring non registered table " + tableName);
                        continue;
                    }
                    throw e;
                }

                /*
                 * disable 'row ID allocation' check for featureclass see
                 * http://jira.codehaus.org/browse/GEOT-3486 for more infos
                 */
                // boolean isSystemTable = reg.getRowIdAllocation() ==
                // SeRegistration.SE_REGISTRATION_ROW_ID_ALLOCATION_SINGLE;
                // if (isSystemTable) {
                // LOGGER.finer("Ignoring ArcSDE registered table " + tableName
                // + " as it is a system table");
                // continue;
                // }

                if (reg.isHidden()) {
                    LOGGER.finer("Ignoring ArcSDE registered table " + tableName
                            + " as it is hidden");
                    continue;
                }

                boolean hasLayer = reg.hasLayer();

                if (!hasLayer) {
                    if (!allowNonSpatialTables) {
                        LOGGER.finer("Ignoring ArcSDE registered table " + tableName
                                + " as it is non spatial");
                        continue;
                    }
                    if (reg.getRowIdColumnType() == SeRegistration.SE_REGISTRATION_ROW_ID_COLUMN_TYPE_NONE) {
                        LOGGER.finer("Ignoring ArcSDE registered table " + tableName
                                + " as it has no row id column");
                        continue;
                    }

                }

                typeNames.add(tableName);
            }

            return typeNames;
        }
    }

}
TOP

Related Classes of org.geotools.arcsde.data.FeatureTypeInfoCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.