/*
* 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.raster.gce;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.arcsde.jndi.SharedSessionPool;
import org.geotools.arcsde.raster.info.GatherCoverageMetadataCommand;
import org.geotools.arcsde.raster.info.RasterDatasetInfo;
import org.geotools.arcsde.raster.io.RasterReaderFactory;
import org.geotools.arcsde.session.ArcSDEConnectionConfig;
import org.geotools.arcsde.session.ISession;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.arcsde.session.SessionPoolFactory;
import org.geotools.arcsde.session.UnavailableConnectionException;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams;
import org.geotools.data.DataSourceException;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.parameter.DefaultParameterDescriptorGroup;
import org.geotools.parameter.ParameterGroup;
import org.geotools.util.logging.Logging;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.parameter.GeneralParameterDescriptor;
/**
* An implementation of the ArcSDE Raster Format. Based on the ArcGrid module.
*
* @author Saul Farber (saul.farber)
* @author jeichar
* @author Simone Giannecchini (simboss)
* @author Gabriel Roldan (OpenGeo)
*
* @source $URL$
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/datastore/src/main/java
* /org/geotools/arcsde/gce/ArcSDERasterFormat.java $
*/
@SuppressWarnings({ "nls", "deprecation" })
public final class ArcSDERasterFormat extends AbstractGridFormat implements Format {
protected static final Logger LOGGER = Logging.getLogger("org.geotools.arcsde.gce");
private final Map<String, ArcSDEConnectionConfig> connectionConfigs = new WeakHashMap<String, ArcSDEConnectionConfig>();
private static final ArcSDERasterFormat instance = new ArcSDERasterFormat();
private boolean statisticsMandatory = true;
/**
* Creates an instance and sets the metadata.
*/
private ArcSDERasterFormat() {
setInfo();
}
public static ArcSDERasterFormat getInstance() {
return instance;
}
/**
* Sets the metadata information.
*/
private void setInfo() {
Map<String, String> info = new HashMap<String, String>();
info.put("name", "ArcSDE Raster");
info.put("description", "ArcSDE Raster Format");
info.put("vendor", "Geotools ");
info.put("docURL", "");
info.put("version", GeoTools.getVersion().toString());
mInfo = info;
readParameters = new ParameterGroup(new DefaultParameterDescriptorGroup(mInfo,
new GeneralParameterDescriptor[] { READ_GRIDGEOMETRY2D, OVERVIEW_POLICY }));
}
/**
* @param source
* either a {@link String} or {@link File} instance representing the connection URL
* @see AbstractGridFormat#getReader(Object source)
*/
@Override
public AbstractGridCoverage2DReader getReader(Object source) {
return getReader(source, null);
}
private static final WeakHashMap<String, ArcSDEGridCoverage2DReaderJAI> readerCache = new WeakHashMap<String, ArcSDEGridCoverage2DReaderJAI>();
private static final ReadWriteLock readersLock = new ReentrantReadWriteLock();
/**
* @param source
* either a {@link String} or {@link File} instance representing the connection URL
* @see AbstractGridFormat#getReader(Object, Hints)
*/
@Override
public AbstractGridCoverage2DReader getReader(final Object source, final Hints hints) {
try {
if (source == null) {
throw new DataSourceException("No source set to read this coverage.");
}
// this will be our connection string
final String coverageUrl = parseCoverageUrl(source);
readersLock.readLock().lock();
ArcSDEGridCoverage2DReaderJAI reader = readerCache.get(coverageUrl);
readersLock.readLock().unlock();
if (reader == null) {
readersLock.writeLock().lock();
try {
reader = readerCache.get(coverageUrl);
if (reader == null) {
final ArcSDEConnectionConfig connectionConfig = getConnectionConfig(coverageUrl);
final ISessionPool sessionPool = setupConnectionPool(connectionConfig);
final RasterDatasetInfo rasterInfo = loadRasterInfo(coverageUrl,
sessionPool);
final RasterReaderFactory rasterReaderFactory = new RasterReaderFactory(
sessionPool);
reader = new ArcSDEGridCoverage2DReaderJAI(this, rasterReaderFactory,
rasterInfo, hints);
readerCache.put(coverageUrl, reader);
}
} finally {
readersLock.writeLock().unlock();
}
}
return reader;
} catch (IOException dse) {
LOGGER.log(Level.SEVERE, "Unable to creata ArcSDERasterReader for " + source + ".", dse);
throw new RuntimeException(dse);
}
}
private RasterDatasetInfo loadRasterInfo(final String coverageUrl, ISessionPool connectionPool)
throws IOException {
final String rasterTable;
{
String sdeUrl = coverageUrl;
if (sdeUrl.indexOf(";") != -1) {
/*
* We're not using any extra param anymore. Yet, be cautious cause a client may
* still be using urls with some old extra param, so just strip it
*/
sdeUrl = sdeUrl.substring(0, sdeUrl.indexOf(";"));
}
rasterTable = sdeUrl.substring(sdeUrl.indexOf("#") + 1);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Building ArcSDEGridCoverageReader2D for " + rasterTable);
}
}
ISession scon;
try {
scon = connectionPool.getSession(false);
} catch (UnavailableConnectionException e) {
throw new RuntimeException(e);
}
RasterDatasetInfo rasterInfo;
try {
GatherCoverageMetadataCommand command = new GatherCoverageMetadataCommand(rasterTable,
statisticsMandatory);
rasterInfo = scon.issue(command);
} finally {
scon.dispose();
}
return rasterInfo;
}
private ArcSDEConnectionConfig getConnectionConfig(final String coverageUrl) {
ArcSDEConnectionConfig sdeConfig;
sdeConfig = connectionConfigs.get(coverageUrl);
if (sdeConfig == null) {
synchronized (connectionConfigs) {
sdeConfig = connectionConfigs.get(coverageUrl);
if (sdeConfig == null) {
sdeConfig = sdeURLToConnectionConfig(new StringBuffer(coverageUrl));
connectionConfigs.put(coverageUrl, sdeConfig);
}
}
}
return sdeConfig;
}
/**
* @see AbstractGridFormat#getWriter(Object)
*/
@Override
public GridCoverageWriter getWriter(Object destination) {
// return new ArcGridWriter(destination);
return null;
}
/**
* @param source
* either a {@link String} or {@link File} instance representing the connection URL
* @see AbstractGridFormat#accepts(Object input)
*/
@Override
public boolean accepts(Object input, Hints hints) {
StringBuffer url;
if (input instanceof File) {
url = new StringBuffer(((File) input).getPath());
} else if (input instanceof String) {
url = new StringBuffer((String) input);
} else {
return false;
}
try {
sdeURLToConnectionConfig(url);
return true;
} catch (Exception e) {
return false;
}
}
/**
* @see Format#getName()
*/
@Override
public String getName() {
return this.mInfo.get("name");
}
/**
* @see Format#getDescription()
*/
@Override
public String getDescription() {
return this.mInfo.get("description");
}
/**
* @see Format#getVendor()
*/
@Override
public String getVendor() {
return this.mInfo.get("vendor");
}
/**
* @see Format#getDocURL()
*/
@Override
public String getDocURL() {
return this.mInfo.get("docURL");
}
/**
* @see Format#getVersion()
*/
@Override
public String getVersion() {
return this.mInfo.get("version");
}
/**
* Retrieves the default instance for the {@link ArcSDERasterFormat} of the
* {@link GeoToolsWriteParams} to control the writing process.
*
* @return a default instance for the {@link ArcSDERasterFormat} of the
* {@link GeoToolsWriteParams} to control the writing process.
* @see AbstractGridFormat#getDefaultImageIOWriteParameters()
*/
@Override
public GeoToolsWriteParams getDefaultImageIOWriteParameters() {
throw new UnsupportedOperationException("ArcSDE Rasters are read only for now.");
}
// ////////////////
/**
* @param input
* either a {@link String} or a {@link File} instance representing the connection URL
* to a given coverage
* @return the connection URL as a string
*/
private String parseCoverageUrl(Object input) {
String coverageUrl;
if (input instanceof String) {
coverageUrl = (String) input;
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("connecting to ArcSDE Raster: " + coverageUrl);
}
} else if (input instanceof File) {
String path = ((File) input).getPath();
while (path.indexOf('\\') > -1) {
path = path.replace('\\', '/');
}
URI uri;
try {
uri = new URI(path);
} catch (URISyntaxException e) {
throw new IllegalArgumentException(path);
}
coverageUrl = uri.toString();
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("connectiong via file-hack to ArcSDE Raster: " + coverageUrl);
}
} else {
throw new IllegalArgumentException("Unsupported input type: " + input.getClass());
}
return coverageUrl;
}
/**
* Checks the input provided to this {@link ArcSDERasterGridCoverage2DReader} and sets all the
* other objects and flags accordingly.
*
* @param sdeUrl
* a url representing the connection parameters to an arcsde server instance provied
* to this {@link ArcSDERasterGridCoverage2DReader}.
* @throws IOException
*/
private ISessionPool setupConnectionPool(ArcSDEConnectionConfig sdeConfig) throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Getting ArcSDE connection pool for " + sdeConfig);
}
ISessionPool sessionPool;
sessionPool = SharedSessionPool.getInstance(sdeConfig, SessionPoolFactory.getInstance());
return sessionPool;
}
public static String createRasterURL(final ArcSDEConnectionConfig config,
final String rasterName) {
StringBuilder sb = new StringBuilder("sde://");
sb.append(config.getUserName()).append(":").append(config.getPassword()).append("@");
sb.append(config.getServerName()).append(":");
sb.append(config.getPortNumber()).append("/");
sb.append(config.getDatabaseName() == null ? "" : config.getDatabaseName());
sb.append("#").append(rasterName);
sb.append(";pool.minConnections=").append(config.getMinConnections());
sb.append(";pool.maxConnections=").append(config.getMaxConnections());
return sb.toString();
}
/**
* @param sdeUrl
* - A StringBuffer containing a string of form
* 'sde://user:pass@sdehost:[port]/[dbname]
* @return a ConnectionConfig object representing these parameters
*/
public static ArcSDEConnectionConfig sdeURLToConnectionConfig(StringBuffer sdeUrl) {
// annoyingly, geoserver currently stores the user-entered SDE string as
// a File, and passes us the
// File object. The File object strips the 'sde://user...' into a
// 'sde:/user..'. So we need to check
// for both forms of the url.
String sdeHost, sdeUser, sdePass, sdeDBName;
int sdePort;
if (sdeUrl.indexOf("sde:/") == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName -- Got "
+ sdeUrl);
}
if (sdeUrl.indexOf("sde://") == -1) {
sdeUrl.delete(0, 5);
} else {
sdeUrl.delete(0, 6);
}
int idx = sdeUrl.indexOf(":");
if (idx == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName[;pool.minConnections=<int>][;pool.maxConnections=<int>]");
}
sdeUser = sdeUrl.substring(0, idx);
sdeUrl.delete(0, idx);
idx = sdeUrl.indexOf("@");
if (idx == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName[;pool.minConnections=<int>][;pool.maxConnections=<int>]");
}
sdePass = sdeUrl.substring(1, idx);
sdeUrl.delete(0, idx);
idx = sdeUrl.indexOf(":");
if (idx == -1) {
// there's no "port" specification. Assume 5151;
sdePort = 5151;
idx = sdeUrl.indexOf("/");
if (idx == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
}
sdeHost = sdeUrl.substring(1, idx).toString();
sdeUrl.delete(0, idx);
} else {
sdeHost = sdeUrl.substring(1, idx).toString();
sdeUrl.delete(0, idx);
idx = sdeUrl.indexOf("/");
if (idx == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
}
sdePort = Integer.parseInt(sdeUrl.substring(1, idx).toString());
sdeUrl.delete(0, idx);
}
idx = sdeUrl.indexOf("#");
if (idx == -1) {
throw new IllegalArgumentException(
"ArcSDE Raster URL must be of the form sde://user:pass@sdehost:port/[dbname]#rasterTableName");
}
sdeDBName = sdeUrl.substring(1, idx).toString();
sdeUrl.delete(0, idx);
String minConnections = "1";
String maxConnections = "10";
if (sdeUrl.indexOf(";") > 0) {
String optionals = sdeUrl.substring(sdeUrl.indexOf(";") + 1);
String[] options = optionals.split(";");
for (String option : options) {
String[] pair = option.split("=");
if (pair.length != 2) {
LOGGER.info("Ignoring malformed optional param '" + option + "'");
continue;
}
String name = pair[0];
String value = pair[1];
if ("pool.minConnections".equals(name)) {
try {
minConnections = Integer.valueOf(value).toString();
} catch (NumberFormatException e) {
LOGGER.warning("Wrong pool.minConnections parameter: " + value);
}
} else if ("pool.maxConnections".equals(name)) {
try {
maxConnections = Integer.valueOf(value).toString();
} catch (NumberFormatException e) {
LOGGER.warning("Wrong pool.maxConnections parameter: " + value);
}
} else {
LOGGER.info("Ignoring unrecognized optional parameter '" + option
+ "'. Must be one of [pool.minConnections, pool.maxConnections]");
}
}
}
Map<String, Serializable> params = new HashMap<String, Serializable>();
params.put(ArcSDEConnectionConfig.SERVER_NAME_PARAM_NAME, sdeHost);
params.put(ArcSDEConnectionConfig.PORT_NUMBER_PARAM_NAME, String.valueOf(sdePort));
params.put(ArcSDEConnectionConfig.INSTANCE_NAME_PARAM_NAME, sdeDBName);
params.put(ArcSDEConnectionConfig.USER_NAME_PARAM_NAME, sdeUser);
params.put(ArcSDEConnectionConfig.PASSWORD_PARAM_NAME, sdePass);
params.put(ArcSDEConnectionConfig.MIN_CONNECTIONS_PARAM_NAME, minConnections);
params.put(ArcSDEConnectionConfig.MAX_CONNECTIONS_PARAM_NAME, maxConnections);
params.put(ArcSDEConnectionConfig.CONNECTION_TIMEOUT_PARAM_NAME, "-1");// do not wait
ArcSDEConnectionConfig config = ArcSDEConnectionConfig.fromMap(params);
return config;
}
/**
* Used by test code to indicate wether to fail when a raster lacks statistics, since we can't
* create statistics with the ArcSDE Java API
*
* @param statisticsMandatory
*/
void setStatisticsMandatory(final boolean statisticsMandatory) {
this.statisticsMandatory = statisticsMandatory;
}
@Override
public GridCoverageWriter getWriter(Object destination, Hints hints) {
return null;
}
}