001: /*
002: * Geotools2 - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2002-2006, Geotools Project Managment Committee (PMC)
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation;
009: * version 2.1 of the License.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: */
017: package org.geotools.arcsde.pool;
018:
019: import java.io.IOException;
020: import java.util.ArrayList;
021: import java.util.HashMap;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.NoSuchElementException;
026: import java.util.logging.Level;
027: import java.util.logging.Logger;
028:
029: import org.apache.commons.pool.BasePoolableObjectFactory;
030: import org.apache.commons.pool.ObjectPool;
031: import org.apache.commons.pool.impl.GenericObjectPool;
032: import org.geotools.data.DataSourceException;
033:
034: import com.esri.sde.sdk.client.SeConnection;
035: import com.esri.sde.sdk.client.SeException;
036: import com.esri.sde.sdk.client.SeInstance;
037: import com.esri.sde.sdk.client.SeLayer;
038: import com.esri.sde.sdk.client.SeRelease;
039: import com.esri.sde.sdk.client.SeTable;
040:
041: /**
042: * Maintains <code>SeConnection</code>'s for a single set of connection
043: * properties (for instance: by server, port, user and password) in a pooled way
044: *
045: * <p>
046: * Since sde connections are not jdbc connections, I can't use Sean's excellent
047: * connection pool. So I'll borrow most of it.
048: * </p>
049: *
050: * <p>
051: * This connection pool is configurable in the sense that some parameters can be
052: * passed to establish the pooling policy. To pass parameters to the connection
053: * pool, you should set some properties in the parameters Map passed to
054: * SdeDataStoreFactory.createDataStore, wich will invoke
055: * SdeConnectionPoolFactory to get the SDE instance's pool singleton. That
056: * instance singleton will be created with the preferences passed the first time
057: * createDataStore is called for a given SDE instance/user, if subsecuent calls
058: * change that preferences, they will be ignored.
059: * </p>
060: *
061: * <p>
062: * The expected optional parameters that you can set up in the argument Map for
063: * createDataStore are:
064: *
065: * <ul>
066: * <li> pool.minConnections Integer, tells the minimun number of open
067: * connections the pool will maintain opened </li>
068: * <li> pool.maxConnections Integer, tells the maximun number of open
069: * connections the pool will create and maintain opened </li>
070: * <li> pool.timeOut Integer, tells how many milliseconds a calling thread is
071: * guaranteed to wait before getConnection() throws an
072: * UnavailableArcSDEConnectionException </li>
073: * </ul>
074: * </p>
075: *
076: * @author Gabriel Roldan, Axios Engineering
077: * @version $Id: ArcSDEConnectionPool.java 27863 2007-11-12 20:34:34Z desruisseaux $
078: */
079: public class ArcSDEConnectionPool {
080: /** package's logger */
081: protected static final Logger LOGGER = org.geotools.util.logging.Logging
082: .getLogger(ArcSDEConnectionPool.class.getName());
083:
084: /** DOCUMENT ME! */
085: protected static final Level INFO_LOG_LEVEL = Level.WARNING;
086:
087: /** default number of connections a pool creates at first population */
088: public static final int DEFAULT_CONNECTIONS = 2;
089:
090: /** default number of maximun allowable connections a pool can hold */
091: public static final int DEFAULT_MAX_CONNECTIONS = 2;
092:
093: public static final int DEFAULT_MAX_WAIT_TIME = 1000;
094:
095: /** DOCUMENT ME! */
096: private SeConnectionFactory seConnectionFactory;
097:
098: /** this connection pool connection's parameters */
099: private ArcSDEConnectionConfig config;
100:
101: /** DOCUMENT ME! */
102: private ObjectPool pool;
103:
104: /**
105: * Holds a cache of tablename/shapeColumn, for all the layers visible for
106: * this pool's connections.
107: */
108: private HashMap /* <String,String> */cachedLayers;
109:
110: /**
111: * Creates a new SdeConnectionPool object with the connection parameters
112: * holded by <code>config</code>
113: *
114: * @param config
115: * holds connection options such as server, user and password, as
116: * well as tuning options as maximun number of connections
117: * allowed
118: *
119: * @throws DataSourceException
120: * DOCUMENT ME!
121: * @throws NullPointerException
122: * DOCUMENT ME!
123: */
124: protected ArcSDEConnectionPool(ArcSDEConnectionConfig config)
125: throws DataSourceException {
126: if (config == null) {
127: throw new NullPointerException(
128: "parameter config can't be null");
129: }
130:
131: this .config = config;
132: this .cachedLayers = new HashMap();
133: LOGGER.fine("populating ArcSDE connection pool");
134:
135: this .seConnectionFactory = new SeConnectionFactory(this .config);
136:
137: int minConnections = config.getMinConnections().intValue();
138: int maxConnections = config.getMaxConnections().intValue();
139: // byte exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_GROW;
140: byte exhaustedAction = GenericObjectPool.WHEN_EXHAUSTED_BLOCK;
141: long maxWait = config.getConnTimeOut().longValue();
142:
143: this .pool = new GenericObjectPool(seConnectionFactory,
144: maxConnections, exhaustedAction, maxWait, true, true);
145: LOGGER.info("Created ArcSDE connection pool for " + config);
146:
147: ArcSDEPooledConnection[] preload = new ArcSDEPooledConnection[minConnections];
148:
149: try {
150: for (int i = 0; i < minConnections; i++) {
151: preload[i] = (ArcSDEPooledConnection) this .pool
152: .borrowObject();
153: }
154:
155: for (int i = 0; i < minConnections; i++) {
156: this .pool.returnObject(preload[i]);
157: }
158: } catch (Exception e) {
159: LOGGER.log(Level.WARNING, "can't connect to " + config, e);
160: throw new DataSourceException(e);
161: }
162: }
163:
164: /**
165: * returns the number of actual connections holded by this connection pool.
166: * In other words, the sum of used and available connections, regardless
167: *
168: * @return DOCUMENT ME!
169: */
170: public int getPoolSize() {
171: synchronized (this .pool) {
172: return this .pool.getNumActive() + this .pool.getNumIdle();
173: }
174: }
175:
176: /**
177: * closes all connections in this pool
178: */
179: public void close() {
180: if (pool != null) {
181: try {
182: this .pool.close();
183: pool = null;
184: LOGGER.fine("SDE connection pool closed. ");
185: } catch (Exception e) {
186: LOGGER.log(Level.WARNING, "Closing pool: "
187: + e.getMessage(), e);
188: }
189: }
190: }
191:
192: /**
193: * Ensures proper closure of connection pool at this object's finalization
194: * stage.
195: */
196: public void finalize() {
197: close();
198: }
199:
200: /**
201: * TODO: Document this method!
202: *
203: * @return DOCUMENT ME!
204: */
205: public synchronized int getAvailableCount() {
206: return this .pool.getNumIdle();
207: }
208:
209: /**
210: * TODO: Document this method!
211: *
212: * @return DOCUMENT ME!
213: */
214: public synchronized int getInUseCount() {
215: return this .pool.getNumActive();
216: }
217:
218: /**
219: * DOCUMENT ME!
220: *
221: * @return DOCUMENT ME!
222: *
223: * @throws DataSourceException
224: * DOCUMENT ME!
225: * @throws UnavailableArcSDEConnectionException
226: * @throws IllegalStateException
227: * DOCUMENT ME!
228: */
229: public ArcSDEPooledConnection getConnection()
230: throws DataSourceException,
231: UnavailableArcSDEConnectionException {
232: if (pool == null) {
233: throw new IllegalStateException(
234: "The ConnectionPool has been closed.");
235: }
236:
237: try {
238: ArcSDEPooledConnection ret = (ArcSDEPooledConnection) this .pool
239: .borrowObject();
240: return ret;
241: } catch (NoSuchElementException e) {
242: LOGGER.log(Level.WARNING, "Out of connections: "
243: + e.getMessage(), e);
244: throw new UnavailableArcSDEConnectionException(this .pool
245: .getNumActive(), this .config);
246: } catch (SeException se) {
247: LOGGER.log(Level.WARNING,
248: "ArcSDE error getting connection: "
249: + se.getSeError().getErrDesc(), se);
250: throw new DataSourceException("ArcSDE Error Message: "
251: + se.getSeError().getErrDesc(), se);
252: } catch (Exception e) {
253: LOGGER.log(Level.WARNING,
254: "Unknown problem getting connection: "
255: + e.getMessage(), e);
256: throw new DataSourceException(
257: "Unknown problem fetching connection from connection pool",
258: e);
259: }
260: }
261:
262: public SeTable getSdeTable(String tableName)
263: throws DataSourceException {
264: ArcSDEPooledConnection conn;
265: try {
266: conn = getConnection();
267: } catch (UnavailableArcSDEConnectionException e) {
268: throw new DataSourceException(e.getMessage(), e);
269: }
270: try {
271: SeTable table = new SeTable(conn, tableName);
272:
273: return table;
274: } catch (SeException ex) {
275: throw new DataSourceException("Can't obtain the table "
276: + tableName + ": " + ex.getMessage(), ex);
277: } finally {
278: conn.close();
279: }
280: }
281:
282: /**
283: * DOCUMENT ME!
284: *
285: * @param tableName
286: * DOCUMENT ME!
287: *
288: * @return DOCUMENT ME!
289: *
290: * @throws DataSourceException
291: * DOCUMENT ME!
292: */
293: public SeTable getSdeTable(SeConnection conn, String tableName)
294: throws DataSourceException {
295: try {
296: SeTable table = new SeTable(conn, tableName);
297:
298: return table;
299: } catch (SeException ex) {
300: throw new DataSourceException("Can't obtain the table "
301: + tableName + ": " + ex.getMessage(), ex);
302: }
303: }
304:
305: public SeLayer getSdeLayer(String typeName)
306: throws NoSuchElementException, IOException {
307: ArcSDEPooledConnection conn = null;
308: SeLayer layer = null;
309:
310: for (int i = 0; i < 3; i++) {
311: //randomly ArcSDE craps out doing this. Catch the nullpointer exception
312: //and try again, I guess.
313: try {
314: try {
315: conn = getConnection();
316: } catch (UnavailableArcSDEConnectionException e) {
317: throw new DataSourceException(e);
318: }
319: try {
320: layer = getSdeLayer(conn, typeName);
321: } finally {
322: conn.close();
323: }
324:
325: } catch (NullPointerException npe) {
326: LOGGER
327: .warning("ArcSDE failed strangely when trying to fetch layer with typename '"
328: + typeName + "' and connection " + conn);
329: markConnectionAsFailed(conn);
330: }
331: }
332: if (layer == null) {
333: throw new DataSourceException(
334: "Failed to fetch ArcSDE Layer " + typeName);
335: } else {
336: return layer;
337: }
338: }
339:
340: /**
341: * Sometimes (and largely without reason) ArcSDEPooledConnections (really their underlying SeConnection objects)
342: * just poop out. They start behaving strangely, or not behaving at all. You can tell the pool that a particular
343: * SeConnection has 'Failed' using this method, and it will do its best to get it out of the pool as soon as you
344: * release your hold on it.
345: *
346: * @param conn
347: */
348: public synchronized void markConnectionAsFailed(
349: ArcSDEPooledConnection conn) {
350: LOGGER
351: .warning("ArcSDE connection '"
352: + conn
353: + "' has been marked as failed. Current pool state is "
354: + getAvailableCount() + " avail/"
355: + this .getPoolSize() + " total");
356: seConnectionFactory.markObjectInvalid(conn);
357: }
358:
359: /**
360: * DOCUMENT ME!
361: *
362: * @param typeName
363: * DOCUMENT ME!
364: *
365: * @return DOCUMENT ME!
366: *
367: * @throws NoSuchElementException
368: * DOCUMENT ME!
369: * @throws IOException
370: * DOCUMENT ME!
371: */
372: public synchronized SeLayer getSdeLayer(SeConnection conn,
373: String typeName) throws NoSuchElementException, IOException {
374:
375: SeLayer layer = null;
376:
377: if (cachedLayers.containsKey(typeName)) {
378:
379: String shapeColumn = (String) cachedLayers.get(typeName);
380: try {
381: layer = new SeLayer(conn, typeName, shapeColumn);
382: } catch (SeException e) {
383: throw new DataSourceException("Getting layer "
384: + typeName, e);
385: }
386:
387: } else {
388: List layers;
389: try {
390: layers = conn.getLayers();
391: for (Iterator it = layers.iterator(); it.hasNext();) {
392: layer = (SeLayer) it.next();
393:
394: if (layer.getQualifiedName().equalsIgnoreCase(
395: typeName)) {
396: break;
397: }
398:
399: layer = null;
400: }
401: } catch (SeException e) {
402: throw new DataSourceException("Getting layer list: "
403: + e.getMessage(), e);
404: }
405:
406: if (layer == null) {
407: throw new NoSuchElementException(typeName);
408: }
409: // cache the layer.
410: this .cachedLayers.put(typeName, layer.getSpatialColumn());
411: }
412:
413: return layer;
414: }
415:
416: /**
417: * Gets the list of available layer names on the database
418: *
419: * @return a <code>List<String></code> with the registered
420: * featureclasses on the ArcSDE database
421: *
422: * @throws DataSourceException
423: */
424: public List /* <String> */getAvailableLayerNames()
425: throws DataSourceException {
426: ArcSDEPooledConnection conn = null;
427:
428: List layerNames = new LinkedList();
429: try {
430: conn = getConnection();
431: for (Iterator it = conn.getLayers().iterator(); it
432: .hasNext();) {
433: layerNames
434: .add(((SeLayer) it.next()).getQualifiedName());
435: }
436: } catch (SeException ex) {
437: throw new DataSourceException(
438: "Error querying the layers list"
439: + ex.getSeError().getSdeError() + " ("
440: + ex.getSeError().getErrDesc() + ") ", ex);
441: } catch (UnavailableArcSDEConnectionException ex) {
442: throw new DataSourceException(
443: "No free connection found to query the layers list",
444: ex);
445: } finally {
446: if (conn != null)
447: conn.close();
448: }
449: return layerNames;
450: }
451:
452: /**
453: * DOCUMENT ME!
454: *
455: * @return DOCUMENT ME!
456: */
457: public ArcSDEConnectionConfig getConfig() {
458: return this .config;
459: }
460:
461: /**
462: * Inner utility class to report the configuration of the ArcSDE service and
463: * the underlying RDBMS pointed by a <code>ArcSDEConnectionConfig</code>
464: * object.
465: *
466: * @author Gabriel Roldan, Axios Engineering
467: * @version $Id: ArcSDEConnectionPool.java 25767 2007-06-07 10:33:44Z
468: * groldan $
469: */
470: private static class SeConfigReport {
471: /**
472: * Reports the configuration of the ArcSDE version and DBMS information
473: * to the logging system, at connection pool's startup, with INFO
474: * logging level.
475: *
476: * @param config
477: * DOCUMENT ME!
478: *
479: * @throws DataSourceException
480: * if a SeException is thrown by the ArcSDE Java API while
481: * trying to fetch the server information.
482: */
483: static void reportConfiguration(ArcSDEConnectionConfig config)
484: throws DataSourceException {
485: try {
486: SeInstance instanceInfo = new SeInstance(config
487: .getServerName(), config.getPortNumber()
488: .intValue());
489:
490: if (!LOGGER.isLoggable(INFO_LOG_LEVEL)) {
491: return;
492: }
493:
494: StringBuffer sb = new StringBuffer(
495: "***\nArcSDE configuration info:\n");
496:
497: sb.append("*** ArcSDE Server info: ****");
498: sb.append("Server name: "
499: + instanceInfo.getServerName());
500:
501: SeInstance.SeInstanceStatus status = instanceInfo
502: .getStatus();
503: SeRelease sdeRelease = status.getSeRelease();
504:
505: sb.append("\n ArcSDE version: ");
506: sb.append(sdeRelease.getMajor());
507: sb.append('.');
508: sb.append(sdeRelease.getMinor());
509: sb.append('.');
510: sb.append(sdeRelease.getBugFix());
511: sb.append(" - ");
512: sb.append(sdeRelease.getDesc());
513:
514: sb.append("\nAccepting connections: ");
515: sb.append(status.isAccepting());
516: sb.append("\nBlocking connections: ");
517: sb.append(status.isBlocking());
518:
519: SeInstance.SeInstanceConfiguration iconf = instanceInfo
520: .getConfiguration();
521: sb.append("\n---- Instance configuration: ----");
522: sb.append("\nInstance is read-only: ");
523: sb.append(iconf.getReadOnlyInstance());
524: sb.append("\nHome path: ");
525: sb.append(iconf.getHomePath());
526: sb.append("\nLog path: ");
527: sb.append(iconf.getLogPath());
528: sb.append("\nMax. connections: ");
529: sb.append(iconf.getMaxConnections());
530: sb.append("\nMax. layers: ");
531: sb.append(iconf.getMaxLayers());
532: sb.append("\nMax. streams: ");
533: sb
534: .append(iconf.getMaxStreams()
535: + " (maximum number of streams allowed by the ArcSde instance)");
536: sb.append("\nStream pool size: ");
537: sb
538: .append(iconf.getStreamPoolSize()
539: + " (maximum number of streams allowed in a native pool.)");
540:
541: sb.append("\n**************************");
542: LOGGER.log(INFO_LOG_LEVEL, sb.toString());
543: } catch (SeException e) {
544: throw new DataSourceException(
545: "Error fetching information from "
546: + " the server "
547: + config.getServerName() + ":"
548: + config.getPortNumber());
549: }
550: }
551: }
552:
553: /**
554: * PoolableObjectFactory intended to be used by a Jakarta's commons-pool
555: * objects pool, that provides ArcSDE's SeConnections.
556: *
557: * @author Gabriel Roldan, Axios Engineering
558: * @version $Id: ArcSDEConnectionPool.java 25767 2007-06-07 10:33:44Z
559: * groldan $
560: */
561: class SeConnectionFactory extends BasePoolableObjectFactory {
562: /** DOCUMENT ME! */
563: private ArcSDEConnectionConfig config;
564:
565: private ArrayList invalidConnections = new ArrayList();
566:
567: /**
568: * Creates a new SeConnectionFactory object.
569: *
570: * @param config
571: * DOCUMENT ME!
572: */
573: public SeConnectionFactory(ArcSDEConnectionConfig config) {
574: super ();
575: this .config = config;
576: }
577:
578: public void markObjectInvalid(Object o) {
579: invalidConnections.add(o);
580: }
581:
582: /**
583: * Called whenever a new instance is needed.
584: *
585: * @return a newly created <code>SeConnection</code>
586: *
587: * @throws SeException
588: * if the connection can't be created
589: */
590: public Object makeObject() throws SeException,
591: DataSourceException {
592: NegativeArraySizeException cause = null;
593: for (int i = 0; i < 3; i++) {
594: try {
595: ArcSDEPooledConnection seConn = new ArcSDEPooledConnection(
596: ArcSDEConnectionPool.this .pool, config);
597: return seConn;
598: } catch (NegativeArraySizeException nase) {
599: LOGGER
600: .warning("Strange failed ArcSDE connection error. Trying again (try "
601: + (i + 1) + " of 3)");
602: cause = nase;
603: }
604: }
605: throw new DataSourceException(
606: "Couldn't create ArcSDEPooledConnection because of strange SDE internal exception. Tried 3 times, giving up.",
607: cause);
608: }
609:
610: /**
611: * is invoked on every instance before it is returned from the pool.
612: *
613: * @param obj
614: */
615: public void activateObject(Object obj) {
616: // no-op
617: LOGGER.finest("activating connection " + obj);
618: }
619:
620: /**
621: * is invoked in an implementation-specific fashion to determine if an
622: * instance is still valid to be returned by the pool. It will only be
623: * invoked on an "activated" instance.
624: *
625: * @param an instance of {@link ArcSDEPooledConnection} maintained by
626: * this pool.
627: *
628: * @return <code>true</code> if the connection is still alive and
629: * operative (checked by asking its user name), <code>false</code>
630: * otherwise.
631: */
632: public boolean validateObject(Object obj) {
633: ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
634: boolean valid = !conn.isClosed();
635: // MAKE PROPER VALIDITY CHECK HERE as for GEOT-1273
636: if (valid) {
637: if (invalidConnections.contains(obj))
638: valid = false;
639:
640: try {
641: LOGGER.finest("Validating SDE Connection");
642: String user = conn.getUser();
643: LOGGER.finer("Connection validated, returned user "
644: + user);
645: } catch (SeException e) {
646: LOGGER
647: .info("Can't validate SeConnection, discarding it: "
648: + conn);
649: valid = false;
650: }
651: }
652: return valid;
653: }
654:
655: /**
656: * is invoked on every instance when it is being "dropped" from the pool
657: * (whether due to the response from validateObject, or for reasons
658: * specific to the pool implementation.)
659: *
660: * @param obj
661: * an instance of {@link ArcSDEPooledConnection} maintained
662: * by this pool.
663: */
664: public void destroyObject(Object obj) {
665: ArcSDEPooledConnection conn = (ArcSDEPooledConnection) obj;
666: conn.destroy();
667: }
668: }
669:
670: public String toString() {
671: StringBuffer ret = new StringBuffer();
672: ret.append("[ACTIVE: ");
673: ret.append(pool.getNumActive() + "/"
674: + ((GenericObjectPool) pool).getMaxActive());
675: ret.append(" INACTIVE: ");
676: ret.append(pool.getNumIdle() + "/"
677: + ((GenericObjectPool) pool).getMaxIdle() + "]");
678: return ret.toString();
679: }
680: }
|