Package org.geotools.arcsde.session

Source Code of org.geotools.arcsde.session.Session

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2002-2008, 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.session;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.geotools.arcsde.ArcSdeException;
import org.geotools.arcsde.logging.Loggers;
import org.geotools.arcsde.versioning.ArcSdeVersionHandler;

import com.esri.sde.sdk.client.SeColumnDefinition;
import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeCoordinateReference;
import com.esri.sde.sdk.client.SeDBMSInfo;
import com.esri.sde.sdk.client.SeDelete;
import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeFilter;
import com.esri.sde.sdk.client.SeInsert;
import com.esri.sde.sdk.client.SeLayer;
import com.esri.sde.sdk.client.SeObjectId;
import com.esri.sde.sdk.client.SeQuery;
import com.esri.sde.sdk.client.SeQueryInfo;
import com.esri.sde.sdk.client.SeRasterColumn;
import com.esri.sde.sdk.client.SeRegistration;
import com.esri.sde.sdk.client.SeRelease;
import com.esri.sde.sdk.client.SeSqlConstruct;
import com.esri.sde.sdk.client.SeState;
import com.esri.sde.sdk.client.SeStreamOp;
import com.esri.sde.sdk.client.SeTable;
import com.esri.sde.sdk.client.SeUpdate;
import com.esri.sde.sdk.geom.GeometryFactory;

/**
* Default implementation of an {@link ISession}
* <p>
* As for the ESRI ArcSDE Java API v9.3.0, the {@link SeQuery#prepareQuery} and
* {@link SeQuery#prepareQueryInfo} methods lead to a memory leak, with {@code SgCoordRef} and
* {@link SeCoordinateReference} instances somehow tied to the {@link SeConnection}. To avoid Heap
* Memory starvation, this {@link Session} will auto-close upon a fixed number of calls to those
* {@code SeQuery} methods, so that the memory can be reclaimed by the garbage collector before it
* becomes a real problem. When that happens, this Session will be marked closed and discarded from
* the {@link SessionPool}, leaving room in the pool to create a new Session as needed.
* </p>
* <p>
* Both the {@link #createAndExecuteQuery} and {@link #prepareQuery} methods will increment the
* auto-close counter.
* <p>
* The default value for the auto-close threshold is {@code 500}. A different value can be specified
* through the {@code "org.geotools.arcsde.session.AutoCloseThreshold"} System property. For
* example, by running your application like
* {@code java -Dorg.geotools.arcsde.session.AutoCloseThreshold=100 -cp... MyApp}
* </p>
*
* @author Gabriel Roldan
* @author Jody Garnett
* @version $Id$
* @since 2.3.x
*/
class Session implements ISession {

    private static final Logger LOGGER = Loggers.getLogger("org.geotools.arcsde.session");

    /**
     * Threshold to be reached by {@link #autoCloseCounter} to automatically recycle (close) the
     * Session and its {@link SeConnection}
     */
    private static final int AUTO_CLOSE_COUNTER_THRESHOLD;
    static {
        Integer systemPropValue = Integer
                .getInteger("org.geotools.arcsde.session.AutoCloseThreshold");
        AUTO_CLOSE_COUNTER_THRESHOLD = systemPropValue == null ? 500 : systemPropValue.intValue();
        LOGGER.info("Session auto-close threshold set to " + AUTO_CLOSE_COUNTER_THRESHOLD);
    }

    /**
     * Counter incremented every time an operation that degrades the performance of the running
     * application is executed, in order to close the {@link SeConnection} when it reaches
     * {@link #AUTO_CLOSE_COUNTER_THRESHOLD}. See class' JavaDocs for more details
     *
     * @see <a href="http://jira.codehaus.org/browse/GEOT-3227">GEOT-3227</a>
     * @see #prepareQuery(SeQueryInfo, SeFilter[], ArcSdeVersionHandler)
     */
    private int autoCloseCounter;

    /**
     * How many seconds must have elapsed since the last connection round trip to the server for
     * {@link #testServer()} to actually check the connection's validity
     */
    protected static final long TEST_SERVER_ROUNDTRIP_INTERVAL_SECONDS = 5;

    /** Actual SeConnection being protected */
    private final SeConnection connection;

    /**
     * SessionPool used to manage open connections (shared).
     */
    private final SessionPool pool;

    private final ArcSDEConnectionConfig config;

    /**
     * Used to assign unique ids to each new session
     */
    private static final AtomicInteger sessionCounter = new AtomicInteger();

    /**
     * Global unique id for this session
     */
    private final int sessionId;

    private boolean transactionInProgress;

    private boolean isPassivated;

    private Map<String, SeTable> cachedTables = new WeakHashMap<String, SeTable>();

    private Map<String, SeLayer> cachedLayers = new WeakHashMap<String, SeLayer>();

    /**
     * Keeps track of the number of references to this session (ie, how many times it has been
     * {@link #markActive() activated} so it's only actually {@link #dispose() disposed} when the
     * reference count gets down to zero.
     */
    private final AtomicInteger referenceCounter = new AtomicInteger();

    /**
     * Provides safe access to an SeConnection.
     *
     * @param pool
     *            SessionPool used to manage SeConnection
     * @param config
     *            Used to set up a SeConnection
     * @throws SeException
     *             If we cannot connect
     */
    Session(final SessionPool pool, final ArcSDEConnectionConfig config) throws IOException {
        this.sessionId = sessionCounter.incrementAndGet();
        this.config = config;
        this.pool = pool;

        final CreateSeConnectionCommand connectionCommand;
        connectionCommand = new CreateSeConnectionCommand(config, sessionId);
        try {
            this.connection = issue(connectionCommand);
        } catch (IOException e) {
            throw e;
        } catch (RuntimeException shouldntHappen) {
            throw shouldntHappen;
        }
    }

    /**
     * @see ISession#issue(org.geotools.arcsde.session.Command)
     */
    public synchronized <T> T issue(final Command<T> command) throws IOException {
        try {
            if (connection == null) {
                return command.execute(this, null);
            } else {
                return command.execute(this, connection);
            }
        } catch (SeException e) {
            throw new ArcSdeException(e);
        }
    }

    /**
     * @see ISession#testServer()
     */
    public final void testServer() throws IOException {
        /*
         * This method is called often (every time a session is to be returned from the pool) to
         * check if it's still valid. We can call getTimeSinceLastRT safely since it does not
         * require a server roundtrip and hence there's no risk of violating thread safety. So we do
         * it before issuing the command to avoid the perf penalty imposed by running the command if
         * not needed.
         */
        final long secondsSinceLastServerRoundTrip = this.connection.getTimeSinceLastRT();

        if (TEST_SERVER_ROUNDTRIP_INTERVAL_SECONDS < secondsSinceLastServerRoundTrip) {
            issue(Commands.TEST_SERVER);
        }
    }

    /**
     * @see ISession#isClosed()
     */
    public final boolean isClosed() {
        return this.connection.isClosed();
    }

    /**
     * Marks the connection as being active (i.e. its out of the pool and ready to be used).
     * <p>
     * Shall be called just before being returned from the connection pool
     * </p>
     *
     * @see #isPassivated
     * @see #checkActive()
     */
    void markActive() {
        referenceCounter.incrementAndGet();
        this.isPassivated = false;
    }

    /**
     * Marks the connection as being inactive (i.e. laying on the connection pool)
     * <p>
     * Shall be callled just before sending it back to the pool
     * </p>
     *
     * @see #markActive()
     * @see #isPassivated
     * @see #checkActive()
     */
    void markInactive() {
        if (referenceCounter.get() != 0) {
            throw new IllegalStateException("referenceCount = " + referenceCounter);
        }
        this.isPassivated = true;
    }

    /**
     * @see ISession#isPassivated()
     */
    public boolean isDisposed() {
        return isPassivated;
    }

    /**
     * Sanity check method called before every public operation delegates to the superclass.
     *
     * @throws IllegalStateException
     *             if {@link #isDisposed() isPassivated() == true} as this is a serious workflow
     *             breackage.
     */
    private void checkActive() {
        if (isDisposed()) {
            throw new IllegalStateException("Unrecoverable error: " + toString()
                    + " is passivated, shall not be used!");
        }
    }

    /**
     * @see ISession#getLayer(java.lang.String)
     */
    public SeLayer getLayer(final String layerName) throws IOException {
        checkActive();
        if (!cachedLayers.containsKey(layerName)) {
            synchronized (cachedLayers) {
                if (!cachedLayers.containsKey(layerName)) {
                    SeTable table = getTable(layerName);
                    SeLayer layer = issue(new Commands.GetLayerCommand(table));
                    if (layer != null) {
                        cachedLayers.put(layerName, layer);
                    }
                }
            }
        }

        SeLayer seLayer = cachedLayers.get(layerName);
        if (seLayer == null) {
            throw new NoSuchElementException("Layer '" + layerName + "' not found");
        }
        return seLayer;

    }

    /**
     * @see ISession#getRasterColumn(java.lang.String)
     */
    public synchronized SeRasterColumn getRasterColumn(final String rasterName) throws IOException {
        throw new UnsupportedOperationException("Waiting for a proper implementation");
    }

    /**
     * @see org.geotools.arcsde.session.ISession#getRasterColumns()
     */
    public List<String> getRasterColumns() throws IOException {
        checkActive();
        List<String> rasterNames = issue(Commands.GET_RASTER_COLUMN_NAMES);
        return rasterNames;
    }

    /**
     * @see ISession#getTable(java.lang.String)
     */
    public SeTable getTable(final String tableName) throws IOException {
        checkActive();
        if (!cachedTables.containsKey(tableName)) {
            synchronized (cachedTables) {
                if (!cachedTables.containsKey(tableName)) {
                    SeTable table = issue(new Commands.GetTableCommand(tableName));
                    cachedTables.put(tableName, table);
                }
            }
        }

        SeTable seTable = (SeTable) cachedTables.get(tableName);

        return seTable;
    }

    /**
     * @see ISession#startTransaction()
     */
    public void startTransaction() throws IOException {
        checkActive();
        issue(Commands.START_TRANSACTION);
        transactionInProgress = true;
    }

    /**
     * @see ISession#commitTransaction()
     */
    public void commitTransaction() throws IOException {
        checkActive();
        issue(Commands.COMMIT_TRANSACTION);
        transactionInProgress = false;
    }

    /**
     * @see ISession#isTransactionActive()
     */
    public boolean isTransactionActive() {
        checkActive();
        return transactionInProgress;
    }

    /**
     * @see ISession#rollbackTransaction()
     */
    public void rollbackTransaction() throws IOException {
        checkActive();
        try {
            issue(Commands.ROLLBACK_TRANSACTION);
        } finally {
            transactionInProgress = false;
        }
    }

    /**
     * @see ISession#dispose()
     */
    public void dispose() throws IllegalStateException {
        checkActive();
        final int refCount = referenceCounter.decrementAndGet();

        if (refCount > 0) {
            // ignore
            if (LOGGER.isLoggable(Level.FINEST)) {
                LOGGER.finest("---------> Ignoring disposal, ref count is still " + refCount
                        + " for " + this);
            }

            // System.err.println("---------> Ignoring disposal, ref count is still " + refCount
            // + " for " + this);
            return;
        }

        if (LOGGER.isLoggable(Level.FINEST)) {
            LOGGER.finest("  -> RefCount is " + refCount + ". Disposing " + this);
        }
        if (transactionInProgress) {
            throw new IllegalStateException(
                    "Transaction is in progress, should commit or rollback before closing");
        }
        if (autoCloseCounter >= AUTO_CLOSE_COUNTER_THRESHOLD) {
            LOGGER.warning("Auto-closing " + this
                    + " to avoid memory leak in ESRI Java API (see GEOT-3227)");
            this.destroy();
        }
        try {
            // System.err.println("---------> Disposing " + this + " on thread " +
            // Thread.currentThread().getName());
            this.pool.returnObject(this);
        } catch (Exception e) {
            LOGGER.log(Level.SEVERE, e.getMessage(), e);
        }
    }

    @Override
    public String toString() {
        return "Session[" + sessionId + "]";
    }

    /**
     * Actually closes the connection, called when the session is discarded from the pool
     */
    void destroy() {
        LOGGER.fine("Destroying connection " + toString());
        try {
            issue(Commands.CLOSE_CONNECTION);
        } catch (Exception e) {
            LOGGER.log(Level.FINE, "closing connection " + toString(), e);
        } finally {
            // taskExecutor.shutdown();
        }
    }

    /**
     * @see ISession#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object other) {
        return other == this;
    }

    /**
     * @see ISession#hashCode()
     */
    @Override
    public int hashCode() {
        return 17 ^ this.config.hashCode();
    }

    /**
     * @see ISession#getLayers()
     */
    public List<SeLayer> getLayers() throws IOException {
        return issue(Commands.GET_LAYERS);
    }

    /**
     * @see ISession#getUser()
     */
    public String getUser() throws IOException {
        return issue(Commands.GET_USER);
    }

    /**
     * @see ISession#getRelease()
     */
    public SeRelease getRelease() throws IOException {
        return issue(Commands.GET_RELEASE);
    }

    /**
     * @see ISession#getDatabaseName()
     */
    public String getDatabaseName() throws IOException {
        return issue(Commands.GET_DATABASENAME);
    }

    /**
     * @see ISession#getDBMSInfo()
     */
    public SeDBMSInfo getDBMSInfo() throws IOException {
        return issue(Commands.GET_DBMS_INFO);
    }

    /**
     * @see ISession#createSeRegistration(java.lang.String)
     */
    public SeRegistration createSeRegistration(final String typeName) throws IOException {
        return issue(new Commands.CreateSeRegistrationCommand(typeName));
    }

    /**
     * @see ISession#createSeTable(java.lang.String)
     */
    public SeTable createSeTable(final String qualifiedName) throws IOException {
        return issue(new Commands.CreateSeTableCommand(qualifiedName));
    }

    /**
     * @see ISession#createSeInsert()
     */
    public SeInsert createSeInsert() throws IOException {
        return issue(Commands.CREATE_SEINSERT);
    }

    /**
     * @see ISession#createSeUpdate()
     */
    public SeUpdate createSeUpdate() throws IOException {
        return issue(Commands.CREATE_SEUPDATE);
    }

    /**
     * @see ISession#createSeDelete()
     */
    public SeDelete createSeDelete() throws IOException {
        return issue(Commands.CREATE_SEDELETE);
    }

    /**
     * @see ISession#describe(java.lang.String)
     */
    public SeColumnDefinition[] describe(final String tableName) throws IOException {
        final SeTable table = getTable(tableName);
        return describe(table);
    }

    /**
     * @see ISession#describe(com.esri.sde.sdk.client.SeTable)
     */
    public SeColumnDefinition[] describe(final SeTable table) throws IOException {
        return issue(new Commands.DescribeTableCommand(table));
    }

    /**
     * @see ISession#fetch(com.esri.sde.sdk.client.SeQuery)
     */
    public SdeRow fetch(final SeQuery query) throws IOException {
        return fetch(query, new SdeRow((GeometryFactory) null));
    }

    public SdeRow fetch(final SeQuery query, final SdeRow currentRow) throws IOException {
        return issue(new Commands.FetchRowCommand(query, currentRow));
    }

    /**
     * @see ISession#close(com.esri.sde.sdk.client.SeState)
     */
    public void close(final SeState state) throws IOException {
        issue(new Commands.CloseStateCommand(state));
    }

    /**
     * @see ISession#close(com.esri.sde.sdk.client.SeStreamOp)
     */
    public void close(final SeStreamOp stream) throws IOException {
        issue(new Commands.CloseStreamCommand(stream));
    }

    /**
     * @see ISession#createState(com.esri.sde.sdk.client.SeObjectId)
     */
    public SeState createState(final SeObjectId stateId) throws IOException {
        return issue(new Commands.CreateSeStateCommand(stateId));
    }

    /**
     * @see ISession#createAndExecuteQuery(java.lang.String[],
     *      com.esri.sde.sdk.client.SeSqlConstruct)
     */
    public SeQuery createAndExecuteQuery(final String[] propertyNames, final SeSqlConstruct sql)
            throws IOException {
        this.autoCloseCounter++;
        return issue(new Commands.CreateAndExecuteQueryCommand(propertyNames, sql));
    }

    /**
     * Creates either a direct child state of parentStateId, or a sibling being an exact copy of
     * parentStatId if either the state can't be closed because its in use or parentStateId does not
     * belong to the current user.
     */
    public SeState createChildState(final long parentStateId) throws IOException {
        return issue(new Commands.CreateVersionStateCommand(parentStateId));
    }

    private static final class CreateSeConnectionCommand extends Command<SeConnection> {
        private final ArcSDEConnectionConfig config;

        private final int sessionId;

        /**
         *
         * @param config
         * @param sessionId
         *            the session id the connection is to be created for. For exception reporting
         *            purposes only
         */
        private CreateSeConnectionCommand(final ArcSDEConnectionConfig config, final int sessionId) {
            this.config = config;
            this.sessionId = sessionId;
        }

        @Override
        public SeConnection execute(final ISession session, final SeConnection connection)
                throws SeException, IOException {
            final String serverName = config.getServerName();
            final String portNumber = config.getPortNumber();
            final String databaseName = config.getDatabaseName();
            final String userName = config.getUserName();
            final String userPassword = config.getPassword();

            NegativeArraySizeException cause = null;
            SeConnection conn = null;
            try {
                for (int i = 0; i < 3; i++) {
                    try {
                        if (LOGGER.isLoggable(Level.FINE)) {
                            LOGGER.fine("Creating connection for session #" + sessionId + "(try "
                                    + (i + 1) + " of 3)");
                        }
                        conn = new SeConnection(serverName, portNumber, databaseName, userName,
                                userPassword);
                        break;
                    } catch (NegativeArraySizeException nase) {
                        LOGGER.warning("Strange failed ArcSDE connection error.  "
                                + "Trying again (try " + (i + 1) + " of 3). SessionId: "
                                + sessionId);
                        cause = nase;
                    }
                }
            } catch (SeException e) {
                throw new ArcSdeException("Can't create connection to " + serverName
                        + " for Session #" + sessionId, e);
            } catch (RuntimeException e) {
                throw (IOException) new IOException("Can't create connection to " + serverName
                        + " for Session #" + sessionId).initCause(e);
            }

            if (cause != null) {
                throw (IOException) new IOException("Couldn't create ArcSDE connection to "
                        + serverName + " for Session #" + sessionId
                        + " because of strange SDE internal exception. "
                        + " Tried 3 times, giving up.").initCause(cause);
            }
            return conn;
        }
    }

    /**
     * @see org.geotools.arcsde.session.ISession#prepareQuery(com.esri.sde.sdk.client.SeQueryInfo,
     *      com.esri.sde.sdk.client.SeFilter[], org.geotools.arcsde.versioning.ArcSdeVersionHandler)
     */
    public SeQuery prepareQuery(final SeQueryInfo qInfo, final SeFilter[] spatialConstraints,
            final ArcSdeVersionHandler version) throws IOException {
        this.autoCloseCounter++;
        return issue(new Commands.PrepareQueryCommand(qInfo, spatialConstraints, version));
    }
}
TOP

Related Classes of org.geotools.arcsde.session.Session

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.