/*
* 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;
import static org.geotools.arcsde.data.ArcSDEDataStoreConfig.ALLOW_NON_SPATIAL_TABLES_PARAM_NAME;
import static org.geotools.arcsde.data.ArcSDEDataStoreConfig.DBTYPE_PARAM_NAME;
import static org.geotools.arcsde.data.ArcSDEDataStoreConfig.NAMESPACE_PARAM_NAME;
import static org.geotools.arcsde.data.ArcSDEDataStoreConfig.VERSION_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.CONNECTION_TIMEOUT_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.INSTANCE_NAME_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.MAX_CONNECTIONS_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.MIN_CONNECTIONS_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.PASSWORD_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.PORT_NUMBER_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.SERVER_NAME_PARAM_NAME;
import static org.geotools.arcsde.session.ArcSDEConnectionConfig.USER_NAME_PARAM_NAME;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import org.geotools.arcsde.data.ArcSDEDataStore;
import org.geotools.arcsde.data.ArcSDEDataStoreConfig;
import org.geotools.arcsde.data.ViewRegisteringFactoryHelper;
import org.geotools.arcsde.session.Commands;
import org.geotools.arcsde.session.ISession;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.arcsde.session.ISessionPoolFactory;
import org.geotools.arcsde.session.SessionPoolFactory;
import org.geotools.arcsde.session.UnavailableConnectionException;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.Parameter;
import org.geotools.util.SimpleInternationalString;
import org.geotools.util.logging.Logging;
import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeRelease;
import com.esri.sde.sdk.pe.PeCoordinateSystem;
import com.esri.sde.sdk.pe.PeFactory;
/**
* Factory to create DataStores over a live ArcSDE instance.
*
* @author Gabriel Roldan, Axios Engineering
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/datastore/src/main/java
* /org/geotools/arcsde/ArcSDEDataStoreFactory.java $
* @version $Id$
*/
@SuppressWarnings("unchecked")
public final class ArcSDEDataStoreFactory implements DataStoreFactorySpi {
/** package's logger */
protected static final Logger LOGGER = Logging
.getLogger(ArcSDEDataStoreFactory.class.getName());
/** friendly factory description */
public static final String FACTORY_DESCRIPTION = "ESRI(tm) ArcSDE 9.2+ vector data store";
/** DOCUMENT ME! */
private static List<Param> paramMetadata = new ArrayList<Param>(10);
public static final int JSDE_VERSION_DUMMY = -1;
public static final int JSDE_VERSION_90 = 0;
public static final int JSDE_VERSION_91 = 1;
public static final int JSDE_VERSION_92 = 2;
private static int JSDE_CLIENT_VERSION;
public static final Param NAMESPACE_PARAM = new Param(NAMESPACE_PARAM_NAME, String.class,
"namespace associated to this data store", false);
public static final Param DBTYPE_PARAM = new Param(DBTYPE_PARAM_NAME, String.class,
"fixed value. Must be \"arcsde\"", true, "arcsde");
public static final Param SERVER_PARAM = new Param(SERVER_NAME_PARAM_NAME, String.class,
"sever name where the ArcSDE gateway is running", true);
public static final Param PORT_PARAM = new Param(
PORT_NUMBER_PARAM_NAME,
Integer.class,
"port number in wich the ArcSDE server is listening for connections.Generally it's 5151",
true, Integer.valueOf(5151));
public static final Param INSTANCE_PARAM = new Param(INSTANCE_NAME_PARAM_NAME, String.class,
"the specific database to connect to. Only applicable to "
+ "certain databases. Value ignored if not applicable.", false);
public static final Param USER_PARAM = new Param(USER_NAME_PARAM_NAME, String.class,
"name of a valid database user account.", true);
public static final Param PASSWORD_PARAM = new Param(PASSWORD_PARAM_NAME, String.class,
new SimpleInternationalString("the database user's password."), false, null,
Collections.singletonMap(Parameter.IS_PASSWORD, Boolean.TRUE));
public static final Param MIN_CONNECTIONS_PARAM = new Param(MIN_CONNECTIONS_PARAM_NAME,
Integer.class, "Minimun number of open connections", false, Integer
.valueOf(ArcSDEDataStoreConfig.DEFAULT_CONNECTIONS));
public static final Param MAX_CONNECTIONS_PARAM = new Param(MAX_CONNECTIONS_PARAM_NAME,
Integer.class, "Maximun number of open connections (will not work if < 2)", false,
Integer.valueOf(ArcSDEDataStoreConfig.DEFAULT_MAX_CONNECTIONS));
public static final Param TIMEOUT_PARAM = new Param(CONNECTION_TIMEOUT_PARAM_NAME,
Integer.class,
"Milliseconds to wait for an available connection before failing to connect", false,
Integer.valueOf(ArcSDEDataStoreConfig.DEFAULT_MAX_WAIT_TIME));
public static final Param VERSION_PARAM = new Param(VERSION_PARAM_NAME, String.class,
"The ArcSDE database version to use.", false);
public static final Param ALLOW_NON_SPATIAL_PARAM = new Param(
ALLOW_NON_SPATIAL_TABLES_PARAM_NAME, Boolean.class,
"If enabled, registered non-spatial tables are also published.", false, Boolean.FALSE);
static {
paramMetadata.add(NAMESPACE_PARAM);
paramMetadata.add(DBTYPE_PARAM);
paramMetadata.add(SERVER_PARAM);
paramMetadata.add(PORT_PARAM);
paramMetadata.add(INSTANCE_PARAM);
paramMetadata.add(USER_PARAM);
paramMetadata.add(PASSWORD_PARAM);
// optional parameters:
paramMetadata.add(MIN_CONNECTIONS_PARAM);
paramMetadata.add(MAX_CONNECTIONS_PARAM);
paramMetadata.add(TIMEOUT_PARAM);
paramMetadata.add(VERSION_PARAM);
paramMetadata.add(ALLOW_NON_SPATIAL_PARAM);
// determine which JSDE api we're running against
determineJsdeVersion();
}
private static void determineJsdeVersion() {
try {
// this class only exists in the dummy api...
Class.forName("com.esri.sde.sdk.GeoToolsDummyAPI");
JSDE_CLIENT_VERSION = JSDE_VERSION_DUMMY;
} catch (Throwable t) {
// good, we're not using the Dummy API placeholder.
try {
// SeDBTune only exists in 9.2
Class.forName("com.esri.sde.sdk.client.SeDBTune");
JSDE_CLIENT_VERSION = JSDE_VERSION_92;
LOGGER.fine("Using ArcSDE API version 9.2 (or higher)");
} catch (Throwable t2) {
// we're using 9.1 or 9.0.
try {
int[] projcss = PeFactory.projcsCodelist();
if (projcss.length == 16380) {
// perhaps I am the hack-master.
JSDE_CLIENT_VERSION = JSDE_VERSION_91;
LOGGER.fine("Using ArcSDE API version 9.1");
} else {
JSDE_CLIENT_VERSION = JSDE_VERSION_90;
LOGGER.fine("Using ArcSDE API version 9.0 (or an earlier 8.x version)");
}
} catch (Throwable crap) {
// not sure what happened here... This next line is
// un-intelligent.
JSDE_CLIENT_VERSION = JSDE_VERSION_90;
}
}
}
}
/** factory of connection pools to different SDE databases */
private static final ISessionPoolFactory poolFactory = SessionPoolFactory.getInstance();
/**
* empty constructor
*/
public ArcSDEDataStoreFactory() {
if (!isAvailable()) {
LOGGER.finest("The ESRI ArcSDE Java API seems to not be on your classpath. Please"
+ " verify that all needed jars are. ArcSDE data stores"
+ " will not be available.");
}
}
/**
* @throws UnsupportedOperationException
* always as the operation is not supported
* @see DataStoreFactorySpi#createNewDataStore(Map)
*/
public DataStore createNewDataStore(java.util.Map map) {
throw new UnsupportedOperationException(
"ArcSDE DataStore does not support the creation of new databases. "
+ "This should be done through database's specific tools");
}
/**
* crates an SdeDataSource based on connection parameters held in <code>params</code>.
* <p>
* Expected parameters are:
* <ul>
* <li>{@code dbtype}: MUST be <code>"arcsde"</code></li>
* <li>{@code server}: machine name where ArcSDE is running</li>
* <li>{@code port}: port number where ArcSDE listens for connections on server</li>
* <li>{@code instance}: database instance name to connect to</li>
* <li>{@code user}: database user name with at least reading privileges over SDE instance</li>
* <li>{@code password}: database user password</li>
* </ul>
* </p>
* <p>
* Optional parameters:
* <ul>
* <li>{@code pool.minConnections}: how many connections to open when the datastore is created
* <li>{@code pool.maxConnections}: max limit of connections for the connection pool
* <li>{@code pool.timeOut}: how many milliseconds to wait for a free connection before failing
* to execute a request
* <li>{@code version}: name of the ArcSDE version for the data store to work upon
* </ul>
* </p>
*
* @param params
* connection parameters
* @return a new <code>SdeDataStore</code> pointing to the database defined by
* <code>params</code>
* @throws java.io.IOException
* if something goes wrong creating the datastore.
*/
public DataStore createDataStore(final Map params) throws java.io.IOException {
if (JSDE_CLIENT_VERSION == JSDE_VERSION_DUMMY) {
throw new DataSourceException("Can't connect to ArcSDE with the dummy jar.");
}
ArcSDEDataStore sdeDStore = null;
ArcSDEDataStoreConfig config = new ArcSDEDataStoreConfig(params);
sdeDStore = createDataStore(config);
ViewRegisteringFactoryHelper.registerSqlViews(sdeDStore, params);
return sdeDStore;
}
final ArcSDEDataStore createDataStore(ArcSDEDataStoreConfig config) throws IOException {
ArcSDEDataStore sdeDStore;
// create a new session pool to be used only by this datastore
final ISessionPool connPool = poolFactory.createPool(config.getSessionConfig());
return createDataStore(config, connPool);
}
final ArcSDEDataStore createDataStore(ArcSDEDataStoreConfig config, final ISessionPool connPool)
throws IOException {
ArcSDEDataStore sdeDStore;
ISession session;
try {
session = connPool.getSession(false);
} catch (UnavailableConnectionException e) {
throw new RuntimeException(e);
}
try {
// check to see if our sdk is compatible with this arcsde instance
SeRelease releaseInfo = session.getRelease();
int majVer = releaseInfo.getMajor();
int minVer = releaseInfo.getMinor();
if (majVer == 9 && minVer > 1 && JSDE_CLIENT_VERSION < JSDE_VERSION_91) {
// we can't connect to ArcSDE 9.2 with the arcsde 9.0 jars. It
// just won't
// work when trying to draw maps. Oh well, at least we'll warn
// people.
LOGGER
.severe("\n\n**************************\n"
+ "DANGER DANGER DANGER!!! You're using the ArcSDE 9.0 (or earlier) jars with "
+ "ArcSDE "
+ majVer
+ "."
+ minVer
+ " on host '"
+ config.getServerName()
+ "' . "
+ "This PROBABLY WON'T WORK. If you have issues "
+ "or unexplained exceptions when rendering maps, upgrade your ArcSDE jars to version "
+ "9.2 or higher. See http://docs.codehaus.org/display/GEOTOOLS/ArcSDE+Plugin\n"
+ "**************************\n\n");
}
// if a version was specified, verify it exists
final String versionName = config.getVersion();
if (versionName != null && !("".equals(versionName.trim()))) {
session.issue(new Commands.GetVersionCommand(versionName));
}
} finally {
session.dispose();
}
String namespaceUri = config.getNamespaceUri();
if (namespaceUri != null && "".equals(namespaceUri.trim())) {
namespaceUri = null;
}
String versionName = config.getVersion();
if (versionName != null && "".equals(versionName.trim())) {
versionName = null;
}
boolean allowNonSpatialTables = config.isAllowNonSpatialTables();
sdeDStore = new ArcSDEDataStore(connPool, namespaceUri, versionName, allowNonSpatialTables);
return sdeDStore;
}
/**
* Display name for this DataStore Factory
*
* @return <code>"ArcSDE"</code>
*/
public String getDisplayName() {
return "ArcSDE";
}
/**
* A human friendly name for this data source factory
*
* @return this factory's description
*/
public String getDescription() {
return FACTORY_DESCRIPTION;
}
/**
* DOCUMENT ME!
*
* @param params
*/
public boolean canProcess(Map params) {
if (JSDE_CLIENT_VERSION == JSDE_VERSION_DUMMY) {
return false;
}
boolean canProcess = true;
try {
new ArcSDEDataStoreConfig(params);
} catch (NullPointerException ex) {
canProcess = false;
} catch (IllegalArgumentException ex) {
canProcess = false;
}
return canProcess;
}
/**
* Test to see if this datastore is available, if it has all the appropriate libraries to
* construct a datastore.
*
* @return <tt>true</tt> if and only if this factory is available to create DataStores.
*/
public boolean isAvailable() {
if (JSDE_CLIENT_VERSION == JSDE_VERSION_DUMMY) {
LOGGER.warning("You must download and install the *real* ArcSDE JSDE jar files. "
+ "Currently the GeoTools ArcSDE 'dummy jar' is on your classpath. "
+ "ArcSDE connectivity is DISABLED. "
+ "See http://docs.codehaus.org/display/GEOTOOLS/ArcSDE+Plugin");
return false;
}
try {
LOGGER.finest(SeConnection.class.getName() + " is in place.");
LOGGER.finest(PeCoordinateSystem.class.getName() + " is in place.");
} catch (Throwable t) {
return false;
}
return true;
}
/**
* @see DataStoreFactorySpi#getParametersInfo()
*/
public DataStoreFactorySpi.Param[] getParametersInfo() {
return paramMetadata.toArray(new Param[paramMetadata.size()]);
}
/**
* Returns the implementation hints. The default implementation returns en empty map.
*/
public Map getImplementationHints() {
return Collections.EMPTY_MAP;
}
public static int getSdeClientVersion() {
return JSDE_CLIENT_VERSION;
}
}