com.sampas.socbs.core.data.arcsde.impl.ArcSDEConnectionPool.java Source code

Java tutorial

Introduction

Here is the source code for com.sampas.socbs.core.data.arcsde.impl.ArcSDEConnectionPool.java

Source

/*
 *    Geotools2 - OpenSource mapping toolkit
 *    http://geotools.org
 *    (C) 2002-2006, Geotools Project Managment Committee (PMC)
 *
 *    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 com.sampas.socbs.core.data.arcsde.impl;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.pool.BasePoolableObjectFactory;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.geotools.data.DataSourceException;
import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeLayer;
import com.esri.sde.sdk.client.SeRelease;

/**
 * Maintains <code>SeConnection</code>'s for a single set of connection
 * properties (for instance: by server, port, user and password) in a pooled way
 * 
 * <p>
 * Since sde connections are not jdbc connections, I can't use Sean's excellent
 * connection pool. So I'll borrow most of it.
 * </p>
 * 
 * <p>
 * This connection pool is configurable in the sense that some parameters can be
 * passed to establish the pooling policy. To pass parameters to the connection
 * pool, you should set some properties in the parameters Map passed to
 * SdeDataStoreFactory.createDataStore, wich will invoke
 * SdeConnectionPoolFactory to get the SDE instance's pool singleton. That
 * instance singleton will be created with the preferences passed the first time
 * createDataStore is called for a given SDE instance/user, if subsecuent calls
 * change that preferences, they will be ignored.
 * </p>
 * 
 * <p>
 * The expected optional parameters that you can set up in the argument Map for
 * createDataStore are:
 * 
 * <ul>
 * <li> pool.minConnections Integer, tells the minimun number of open
 * connections the pool will maintain opened </li>
 * <li> pool.maxConnections Integer, tells the maximun number of open
 * connections the pool will create and maintain opened </li>
 * <li> pool.timeOut Integer, tells how many milliseconds a calling thread is
 * guaranteed to wait before getConnection() throws an
 * UnavailableArcSDEConnectionException </li>
 * </ul>
 * </p>
 * 
 * @author Gabriel Roldan, Axios Engineering
 * @version $Id: ArcSDEConnectionPool.java 29625 2008-03-14 13:55:02Z groldan $
 */
@SuppressWarnings("unchecked")
public class ArcSDEConnectionPool {
    /** package's logger */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.geotools.arcsde.pool");

    /** DOCUMENT ME! */
    protected static final Level INFO_LOG_LEVEL = Level.WARNING;

    /** default number of connections a pool creates at first population */
    public static final int DEFAULT_CONNECTIONS = 2;

    /** default number of maximun allowable connections a pool can hold */
    public static final int DEFAULT_MAX_CONNECTIONS = 6;

    public static final int DEFAULT_MAX_WAIT_TIME = 500;

    /** DOCUMENT ME! */
    private SeConnectionFactory seConnectionFactory;

    /** this connection pool connection's parameters */
    private ArcSDEConnectionConfig config;

    /** Apache commons-pool used to pool arcsde connections */
    private ObjectPool pool;

    /**
     * Creates a new SdeConnectionPool object with the connection parameters
     * holded by <code>config</code>
     * 
     * @param config
     *            holds connection options such as server, user and password, as
     *            well as tuning options as maximun number of connections
     *            allowed
     * 
     * @throws DataSourceException
     *             DOCUMENT ME!
     * @throws NullPointerException
     *             DOCUMENT ME!
     */
    protected ArcSDEConnectionPool(ArcSDEConnectionConfig config) throws DataSourceException {
        if (config == null) {
            throw new NullPointerException("parameter config can't be null");
        }

        this.config = config;
        LOGGER.fine("populating ArcSDE connection pool");

        this.seConnectionFactory = new SeConnectionFactory(this.config);

        int minConnections = config.getMinConnections().intValue();
        int maxConnections = config.getMaxConnections().intValue();
        // byte exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
        byte exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
        long maxWait = config.getConnTimeOut().longValue();

        this.pool = new GenericObjectPool(seConnectionFactory, maxConnections, exhaustedAction, maxWait, true,
                true);
        LOGGER.info("Created ArcSDE connection pool for " + config);

        ArcSDEPooledConnection[] preload = new ArcSDEPooledConnection[minConnections];

        try {
            for (int i = 0; i < minConnections; i++) {
                preload[i] = (ArcSDEPooledConnection) this.pool.borrowObject();
                if (i == 0) {
                    SeRelease seRelease = preload[i].getRelease();
                    String sdeDesc = seRelease.getDesc();
                    int major = seRelease.getMajor();
                    int minor = seRelease.getMinor();
                    int bugFix = seRelease.getBugFix();
                    String desc = "ArcSDE " + major + "." + minor + "." + bugFix + " " + sdeDesc;
                    LOGGER.info("Connected to " + desc);
                }
            }

            for (int i = 0; i < minConnections; i++) {
                this.pool.returnObject(preload[i]);
            }
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "can't connect to " + config, e);
            throw new DataSourceException(e);
        }
    }

    /**
     * returns the number of actual connections holded by this connection pool.
     * In other words, the sum of used and available connections, regardless
     * 
     * @return DOCUMENT ME!
     */
    public int getPoolSize() {
        synchronized (this.pool) {
            return this.pool.getNumActive() + this.pool.getNumIdle();
        }
    }

    /**
     * closes all connections in this pool
     */
    public void close() {
        if (pool != null) {
            try {
                this.pool.close();
                pool = null;
                LOGGER.fine("SDE connection pool closed. ");
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Closing pool: " + e.getMessage(), e);
            }
        }
    }

    /**
     * Ensures proper closure of connection pool at this object's finalization
     * stage.
     */
    //@Override
    protected void finalize() {
        close();
    }

    /**
     * TODO: Document this method!
     * 
     * @return DOCUMENT ME!
     */
    public synchronized int getAvailableCount() {
        return this.pool.getNumIdle();
    }

    /**
     * TODO: Document this method!
     * 
     * @return DOCUMENT ME!
     */
    public synchronized int getInUseCount() {
        return this.pool.getNumActive();
    }

    /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     * 
     * @throws DataSourceException
     *             DOCUMENT ME!
     * @throws UnavailableArcSDEConnectionException
     * @throws IllegalStateException
     *             DOCUMENT ME!
     */
    public ArcSDEPooledConnection getConnection() throws DataSourceException, UnavailableArcSDEConnectionException {
        if (pool == null) {
            throw new IllegalStateException("The ConnectionPool has been closed.");
        }

        try {
            // String caller = null;
            // if (LOGGER.isLoggable(Level.FINER)) {
            // StackTraceElement[] stackTrace =
            // Thread.currentThread().getStackTrace();
            // caller = stackTrace[3].getClassName() + "." +
            // stackTrace[3].getMethodName();
            // }

            ArcSDEPooledConnection connection = (ArcSDEPooledConnection) this.pool.borrowObject();

            if (LOGGER.isLoggable(Level.FINER)) {
                // System.err.println("-> " + caller + " got " + connection);
                LOGGER.finer(connection + " out of connection pool");
            }

            connection.markActive();
            return connection;
        } catch (NoSuchElementException e) {
            LOGGER.log(Level.WARNING, "Out of connections: " + e.getMessage(), e);
            throw new UnavailableArcSDEConnectionException(this.pool.getNumActive(), this.config);
        } catch (SeException se) {
            LOGGER.log(Level.WARNING, "ArcSDE error getting connection: " + se.getSeError().getErrDesc(), se);
            throw new DataSourceException("ArcSDE Error Message: " + se.getSeError().getErrDesc(), se);
        } catch (Exception e) {
            LOGGER.log(Level.WARNING, "Unknown problem getting connection: " + e.getMessage(), e);
            throw new DataSourceException("Unknown problem fetching connection from connection pool", e);
        }
    }

    /**
     * Sometimes (and largely without reason) ArcSDEPooledConnections (really
     * their underlying SeConnection objects) just poop out. They start behaving
     * strangely, or not behaving at all. You can tell the pool that a
     * particular SeConnection has 'Failed' using this method, and it will do
     * its best to get it out of the pool as soon as you release your hold on
     * it.
     * 
     * @param conn
     */
    public synchronized void markConnectionAsFailed(ArcSDEPooledConnection conn) {
        LOGGER.warning("ArcSDE connection '" + conn + "' has been marked as failed.  Current pool state is "
                + getAvailableCount() + " avail/" + this.getPoolSize() + " total");
        seConnectionFactory.markObjectInvalid(conn);
    }

    /**
     * Gets the list of available layer names on the database
     * 
     * @return a <code>List&lt;String&gt;</code> with the registered
     *         featureclasses on the ArcSDE database
     * 
     * @throws DataSourceException
     */
    //@SuppressWarnings("unchecked")
    public List/*<String>*/ getAvailableLayerNames() throws DataSourceException {
        ArcSDEPooledConnection conn = null;

        List/*<String>*/ layerNames = new LinkedList/*<String>*/();
        try {
            conn = getConnection();
            for (Iterator /*<SeLayer>*/ it = conn.getLayers().iterator(); it.hasNext();) {
                layerNames.add(((SeLayer) it.next()).getQualifiedName());
            }
        } catch (SeException ex) {
            throw new DataSourceException("Error querying the layers list" + ex.getSeError().getSdeError() + " ("
                    + ex.getSeError().getErrDesc() + ") ", ex);
        } catch (UnavailableArcSDEConnectionException ex) {
            throw new DataSourceException("No free connection found to query the layers list", ex);
        } finally {
            if (conn != null)
                conn.close();
        }
        return layerNames;
    }

    /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
    public ArcSDEConnectionConfig getConfig() {
        return this.config;
    }

    /**
     * PoolableObjectFactory intended to be used by a Jakarta's commons-pool
     * objects pool, that provides ArcSDE's SeConnections.
     * 
     * @author Gabriel Roldan, Axios Engineering
     * @version $Id: ArcSDEConnectionPool.java 25767 2007-06-07 10:33:44Z
     *          groldan $
     */
    class SeConnectionFactory extends BasePoolableObjectFactory {
        /** DOCUMENT ME! */
        private ArcSDEConnectionConfig config;

        private List/*<SeConnection>*/ invalidConnections = new ArrayList/*<SeConnection>*/(2);

        /**
         * Creates a new SeConnectionFactory object.
         * 
         * @param config
         *            DOCUMENT ME!
         */
        public SeConnectionFactory(ArcSDEConnectionConfig config) {
            super();
            this.config = config;
        }

        public void markObjectInvalid(Object o) {
            invalidConnections.add((SeConnection) o);
        }

        /**
         * Called whenever a new instance is needed.
         * 
         * @return a newly created <code>SeConnection</code>
         * 
         * @throws SeException
         *             if the connection can't be created
         */
        //@Override
        public Object makeObject() throws IOException {
            NegativeArraySizeException cause = null;
            for (int i = 0; i < 3; i++) {
                try {
                    ArcSDEPooledConnection seConn = new ArcSDEPooledConnection(ArcSDEConnectionPool.this.pool,
                            config);
                    return seConn;
                } catch (NegativeArraySizeException nase) {
                    LOGGER.warning(
                            "Strange failed ArcSDE connection error.  Trying again (try " + (i + 1) + " of 3)");
                    cause = nase;
                } catch (SeException e) {
                    throw new ArcSdeException(e);
                }
            }
            throw new DataSourceException(
                    "Couldn't create ArcSDEPooledConnection because of strange SDE internal exception.  Tried 3 times, giving up.",
                    cause);
        }

        /**
         * is invoked on every instance before it is returned from the pool.
         * 
         * @param obj
         */
        //@Override
        public void activateObject(Object obj) {
            final ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
            conn.markActive();
            LOGGER.finest("activating connection " + obj);
        }

        //@Override
        public void passivateObject(Object obj) {
            LOGGER.finest("passivating connection " + obj);
            final ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
            conn.markInactive();
        }

        /**
         * is invoked in an implementation-specific fashion to determine if an
         * instance is still valid to be returned by the pool. It will only be
         * invoked on an "activated" instance.
         * 
         * @param an
         *            instance of {@link ArcSDEPooledConnection} maintained by
         *            this pool.
         * 
         * @return <code>true</code> if the connection is still alive and
         *         operative (checked by asking its user name),
         *         <code>false</code> otherwise.
         */
        //@Override
        public boolean validateObject(Object obj) {
            ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
            boolean valid = !conn.isClosed();
            // MAKE PROPER VALIDITY CHECK HERE as for GEOT-1273
            if (valid) {
                if (invalidConnections.contains(obj))
                    valid = false;

                try {
                    LOGGER.finest("Validating SDE Connection");
                    String user = conn.getUser();
                    LOGGER.finer("Connection validated, returned user " + user);
                } catch (SeException e) {
                    LOGGER.info("Can't validate SeConnection, discarding it: " + conn);
                    valid = false;
                }
            }
            return valid;
        }

        /**
         * is invoked on every instance when it is being "dropped" from the pool
         * (whether due to the response from validateObject, or for reasons
         * specific to the pool implementation.)
         * 
         * @param obj
         *            an instance of {@link ArcSDEPooledConnection} maintained
         *            by this pool.
         */
        //@Override
        public void destroyObject(Object obj) {
            ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
            conn.destroy();
        }
    }

    //@Override
    public String toString() {
        StringBuffer ret = new StringBuffer();
        ret.append("[ACTIVE: ");
        ret.append(pool.getNumActive() + "/" + ((GenericObjectPool) pool).getMaxActive());
        ret.append("  INACTIVE: ");
        ret.append(pool.getNumIdle() + "/" + ((GenericObjectPool) pool).getMaxIdle() + "]");
        return ret.toString();
    }
}