/*
* 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;
import static org.geotools.arcsde.ArcSDEDataStoreFactory.ALLOW_NON_SPATIAL_PARAM;
import static org.geotools.arcsde.ArcSDEDataStoreFactory.NAMESPACE_PARAM;
import static org.geotools.arcsde.ArcSDEDataStoreFactory.VERSION_PARAM;
import java.awt.RenderingHints.Key;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.NamingException;
import org.geotools.arcsde.data.ArcSDEDataStore;
import org.geotools.arcsde.data.ArcSDEDataStoreConfig;
import org.geotools.arcsde.jndi.ArcSDEConnectionFactory;
import org.geotools.arcsde.session.ArcSDEConnectionConfig;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.factory.GeoTools;
import org.geotools.util.logging.Logging;
/**
* A GeoTools {@link DataStore} factory to access an ArcSDE database by grabbing the connection pool
* from a JNDI reference.
* <p>
* This DataStore factory expects the arcsde connection information to be given as a JNDI resource
* path through the {@link #JNDI_REFNAME jndiRefName} parameter at {@link #createDataStore(Map)}.
* The resource provided by the JNDI context at that location may be either:
* <ul>
* <li>a {@code java.util.Map<String, String>} with the connection parameters from
* {@link ArcSDEConnectionConfig}. If so, the {@link ISessionPool session pool} will be taken from
* {@link ArcSDEConnectionFactory#getInstance(Map)}</li>
* <li>a {@link ISessionPool} instance</li>
* </ul>
* </p>
* <p>
* If not an {@code ISessionPool}, the object will be used to get one from
* {@link ArcSDEConnectionFactory}. Whether the resulting session (connection) pool is shared among
* {@link ArcSDEDataStore} instances is dependent on how the JNDI resource is externally configured.
* For example, on the J2EE container, it will depend on if the JNDI resource is globally configured
* or not, and the required jar files are on a J2EE container shared libraries folder or not.
* </p>
*
* @author Gabriel Roldan (OpenGeo)
*
*
* @source $URL$
* http://svn.osgeo.org/geotools/trunk/modules/plugin/arcsde/datastore/src/main/java/org
* /geotools/arcsde/ArcSDEJNDIDataStoreFactory.java $
* @version $Id$
* @since 2.5.7
*/
public class ArcSDEJNDIDataStoreFactory implements DataStoreFactorySpi {
private static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde");
private final ArcSDEDataStoreFactory delegateFactory;
/**
* JNDI context path name
*/
public static final Param JNDI_REFNAME = new Param("ArcSDE_jndiReferenceName", String.class,
"JNDI context path", true, "java:comp/env/geotools/arcsde");
private static final String J2EE_ROOT_CONTEXT = "java:comp/env/";
public ArcSDEJNDIDataStoreFactory() {
this.delegateFactory = new ArcSDEDataStoreFactory();
}
/**
* Creates and {@link ArcSDEDataStore} from the provided {@code params}, where the connection
* pool is provided by JNDI.
* <p>
* See {@link #getParametersInfo()} to check which datastore creation parameters are expected by
* this factory method.
* </p>
*
* @see org.geotools.data.DataStoreFactorySpi#createDataStore(java.util.Map)
*/
public DataStore createDataStore(Map<String, Serializable> params) throws IOException {
final String jndiName = (String) JNDI_REFNAME.lookUp(params);
final Object lookup = lookupJndiResource(jndiName);
final ISessionPool sessionPool = getSessionPool(jndiName, lookup);
final String nsUri = (String) NAMESPACE_PARAM.lookUp(params);
final String version = (String) VERSION_PARAM.lookUp(params);
final boolean nonSpatial;
{
final Boolean allowNonSpatialTables = (Boolean) ALLOW_NON_SPATIAL_PARAM.lookUp(params);
nonSpatial = allowNonSpatialTables == null ? false : allowNonSpatialTables
.booleanValue();
}
final ArcSDEConnectionConfig connectionConfig = sessionPool.getConfig();
final ArcSDEDataStoreConfig dsConfig;
dsConfig = new ArcSDEDataStoreConfig(connectionConfig, nsUri, version, nonSpatial);
LOGGER.info("Creating ArcSDE JNDI DataStore with shared session pool for " + dsConfig);
final ArcSDEDataStore dataStore = delegateFactory.createDataStore(dsConfig, sessionPool);
return dataStore;
}
@SuppressWarnings("unchecked")
private ISessionPool getSessionPool(final String jndiName, final Object lookup)
throws IOException, DataSourceException {
final ISessionPool sessionPool;
if (lookup instanceof ISessionPool) {
LOGGER.info("Creating JNDI ArcSDE DataStore with shared session pool for " + lookup);
sessionPool = (ISessionPool) lookup;
} else if (lookup instanceof Map) {
Map<String, Serializable> map = new HashMap<String, Serializable>();
{
Map<Object, Object> props = (Map<Object, Object>) lookup;
String key;
Object value;
for (Map.Entry<Object, Object> e : props.entrySet()) {
key = String.valueOf(e.getKey());
value = e.getValue();
map.put(key, value == null ? null : String.valueOf(e.getValue()));
}
}
// use the JNDI factory to grab the shared pool
ArcSDEConnectionFactory factory = new ArcSDEConnectionFactory();
sessionPool = factory.getInstance(map);
} else {
throw new DataSourceException("Unknown JNDI resource on path " + jndiName
+ ". Expected one of [" + ArcSDEConnectionConfig.class.getName() + ", "
+ ISessionPool.class.getName() + "] but got " + lookup.getClass().getName()
+ " (" + lookup + ")");
}
return sessionPool;
}
/**
* Looks up and returns the JNDI resource addressed by {@code jndiName}
*
* @param jndiName
* @return the resource mapped at {@code jndiName}, which shall be either a
* {@code java.util.Map<String, String>}, an {@link ArcSDEConnectionConfig} or a
* {@link ISessionPool}.
* @throws IOException
* if a resource is not found at {@code jndiName}
*/
private Object lookupJndiResource(final String jndiName) throws IOException {
if (jndiName == null) {
throw new IOException("Missing " + JNDI_REFNAME.description);
}
final Context ctx;
try {
ctx = GeoTools.getInitialContext(GeoTools.getDefaultHints());
} catch (NamingException e) {
throw new RuntimeException(e);
}
Object lookup = null;
try {
lookup = ctx.lookup(jndiName);
} catch (NamingException e1) {
// check if the user did not specify "java:comp/env"
// and this code is running in a J2EE environment
try {
if (jndiName.startsWith(J2EE_ROOT_CONTEXT) == false) {
lookup = ctx.lookup(J2EE_ROOT_CONTEXT + jndiName);
// success --> issue a waring
LOGGER.warning("Using " + J2EE_ROOT_CONTEXT + jndiName + " instead of "
+ jndiName + " would avoid an unnecessary JNDI lookup");
}
} catch (NamingException e2) {
// do nothing, was only a try
}
}
if (lookup == null) {
throw new IOException("Cannot find JNDI data source: " + jndiName);
}
return lookup;
}
/**
* Returns whether this factory <i>could</i> process the given parameters. That is, it does not
* check the validity of the parameter, it only asserts the {@link #JNDI_REFNAME} parameter is
* present. That is so so any failure is handled by {@link #createDataStore(Map)} instead of
* getting client code silently failing (as this method does not throw an exception)
*
* @see org.geotools.data.DataAccessFactory#canProcess(java.util.Map)
*/
public boolean canProcess(Map<String, Serializable> params) {
if (params == null) {
return false;
}
String lookUpKey;
try {
lookUpKey = (String) JNDI_REFNAME.lookUp(params);
} catch (IOException e) {
return false;
}
if (lookUpKey == null) {
return false;
}
return true;
}
/**
* @see org.geotools.data.DataAccessFactory#getDescription()
*/
public String getDescription() {
return delegateFactory.getDescription() + " (JNDI)";
}
/**
* @see org.geotools.data.DataAccessFactory#getDisplayName()
*/
public String getDisplayName() {
return delegateFactory.getDisplayName() + " (JNDI)";
}
/**
* Provides the datastore creation parameter metadata for this factory.
* <p>
* The returned parameters are:
* <ul>
* <li>{@link #JNDI_REFNAME jndiReferenceName}: the JNDI path to the {@link ISessionPool
* connection pool}
* <li> {@link ArcSDEDataStoreConfig#NAMESPACE_PARAM_NAME namespace}: the namespace uri the
* datastore should create feature types in
* <li> {@link ArcSDEDataStoreConfig#VERSION_PARAM_NAME database.version} the arcsde database
* version the datastore shall work upon. If non provided or empty, the DEFAULT version will be
* used.
* <li> {@link ArcSDEDataStoreConfig#ALLOW_NON_SPATIAL_TABLES_PARAM_NAME
* datastore.allowNonSpatialTables} whether to publish non spatial registered tables (aka,
* Object Classes). Defaults to {@code false}.
* </ul>
* </p>
*
* @see org.geotools.data.DataAccessFactory#getParametersInfo()
*/
public Param[] getParametersInfo() {
return new Param[] { JNDI_REFNAME, NAMESPACE_PARAM, VERSION_PARAM, ALLOW_NON_SPATIAL_PARAM };
}
/**
* Determines if the datastore is available.
* <p>
* Check in an Initial Context is available, that is all what can be done Checking for the right
* jdbc jars in the classpath is not possible here
* </p>
*
* @see org.geotools.data.DataAccessFactory#isAvailable()
*/
public boolean isAvailable() {
try {
GeoTools.getInitialContext(GeoTools.getDefaultHints());
} catch (NamingException e) {
return false;
}
return delegateFactory.isAvailable();
}
/**
* @see org.geotools.factory.Factory#getImplementationHints()
*/
public Map<Key, ?> getImplementationHints() {
return delegateFactory.getImplementationHints();
}
/**
* @see org.geotools.data.DataStoreFactorySpi#createNewDataStore(java.util.Map)
*/
public DataStore createNewDataStore(Map<String, Serializable> params) throws IOException {
throw new UnsupportedOperationException("ArcSDE PlugIn does not support createNewDataStore");
}
}