/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 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.data;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import org.geotools.factory.Factory;
import org.geotools.util.KVP;
import org.geotools.util.SimpleInternationalString;
import org.opengis.feature.Feature;
import org.opengis.feature.type.FeatureType;
import org.opengis.util.InternationalString;
/**
* Constructs a live DataAccess from a set of connection parameters.
* <p>
* The following example shows how a user might connect to a PostGIS database,
* and maintain the resulting datastore in a Registry:
* </p>
*
* <p>
* <pre><code>
* HashMap params = new HashMap();
* params.put("namespace", "leeds");
* params.put("dbtype", "postgis");
* params.put("host","feathers.leeds.ac.uk");
* params.put("port", "5432");
* params.put("database","postgis_test");
* params.put("user","postgis_ro");
* params.put("passwd","postgis_ro");
*
* DefaultRegistry registry = new DefaultRegistry();
* registry.addDataStore("leeds", params);
*
* DataStore postgis = registry.getDataStore( "leeds" );
* FeatureSource<SimpleFeatureType, SimpleFeature> = postgis.getFeatureSource( "table" );
* </code></pre>
* </p>
* The required parameters are described by the getParameterInfo() method. Client
*
* <h2>Implementation Notes</h2>
* <p>
* An instance of this interface should exist for all DataAccess implementations
* that want to advantage of the dynamic plug-in system. In addition to implementing
* this interface a DataAccess implementation should provide a services file:
* </p>
*
* <p>
* <code>META-INF/services/org.geotools.data.DataAccessFactory</code>
* </p>
*
* <p>
* The file should contain a single line which gives the full name of the
* implementing class.
* </p>
*
* <p>
* Example:<br/><code>e.g.
* org.geotools.data.mytype.MyTypeDataSourceFacotry</code>
* </p>
*
* <p>
* The factories are never called directly by client code, instead the
* DataStoreFinder class is used.
* </p>
*
* @author Jody Garnett (Refractions Research)
* @source $URL$
*/
public interface DataAccessFactory extends Factory {
/**
* Construct a live DataAccess using the connection parameters provided.
* <p>
* You can think of this class as setting up a connection to the back end data source. The
* required parameters are described by the getParameterInfo() method.
* </p>
*
* <p>
* Magic Params: the following params are magic and are honoured by
* convention by the GeoServer and uDig application.
*
* <ul>
* <li>
* "user": is taken to be the user name
* </li>
* <li>
* "passwd": is taken to be the password
* </li>
* <li>
* "namespace": is taken to be the namespace prefix (and will be kept in
* sync with GeoServer namespace management.
* </li>
* </ul>
*
* When we eventually move over to the use of OpperationalParam we will
* have to find someway to codify this convention.
* </p>
*
* @param params The full set of information needed to construct a live
* data store. Typical key values for the map include: url -
* location of a resource, used by file reading datasources. dbtype
* - the type of the database to connect to, e.g. postgis, mysql
*
* @return The created DataStore, this may be null if the required resource
* was not found or if insufficent parameters were given. Note
* that canProcess() should have returned false if the problem is
* to do with insuficent parameters.
*
* @throws IOException if there were any problems setting up (creating or
* connecting) the datasource.
*/
DataAccess<? extends FeatureType, ? extends Feature> createDataStore(Map<String, Serializable> params) throws IOException;
/**
* Name suitable for display to end user.
*
* <p>
* A non localized display name for this data store type.
* </p>
*
* @return A short name suitable for display in a user interface.
*/
String getDisplayName();
/**
* Describe the nature of the datasource constructed by this factory.
*
* <p>
* A non localized description of this data store type.
* </p>
*
* @return A human readable description that is suitable for inclusion in a
* list of available datasources.
*/
String getDescription();
/**
* MetaData about the required Parameters (for createDataStore).
*
* <p>
* Interpretation of FeatureDescriptor values:
* </p>
*
* <ul>
* <li>
* getDisplayName(): Gets the localized display name of this feature.
* </li>
* <li>
* getName(): Gets the programmatic name of this feature (used as the key
* in params)
* </li>
* <li>
* getShortDescription(): Gets the short description of this feature.
* </li>
* </ul>
*
* <p>
* This should be the same as:
* </p>
* <pre><code>
* Object params = factory.getParameters();
* BeanInfo info = getBeanInfo( params );
*
* return info.getPropertyDescriptors();
* <code></pre>
*
* @return Param array describing the Map for createDataStore
*/
Param[] getParametersInfo();
/**
* Test to see if this factory is suitable for processing the data pointed
* to by the params map.
*
* <p>
* If this datasource requires a number of parameters then this mehtod
* should check that they are all present and that they are all valid. If
* the datasource is a file reading data source then the extentions or
* mime types of any files specified should be checked. For example, a
* Shapefile datasource should check that the url param ends with shp,
* such tests should be case insensative.
* </p>
*
* @param params The full set of information needed to construct a live
* data source.
*
* @return booean true if and only if this factory can process the resource
* indicated by the param set and all the required params are
* pressent.
*/
boolean canProcess(java.util.Map<String, Serializable> params);
/**
* Test to see if the implementation is available for use.
* This method ensures all the appropriate libraries to construct
* the DataAccess are available.
* <p>
* Most factories will simply return <code>true</code> as GeoTools will
* distribute the appropriate libraries. Though it's not a bad idea for
* DataStoreFactories to check to make sure that the libraries are there.
* <p>
* OracleDataStoreFactory is an example of one that may generally return
* <code>false</code>, since GeoTools can not distribute the oracle jars.
* (they must be added by the client.)
* <p>
* One may ask how this is different than canProcess, and basically available
* is used by the DataStoreFinder getAvailableDataStore method, so that
* DataStores that can not even be used do not show up as options in gui
* applications.
*
* @return <tt>true</tt> if and only if this factory has all the
* appropriate jars on the classpath to create DataStores.
*/
boolean isAvailable();
/**
* Data class used to capture Parameter requirements.
*
* <p>
* Subclasses may provide specific setAsText()/getAsText() requirements
* </p>
*
* <p>
* Warning: We would like to start moving towards a common paraemters
* framework with GridCoverageExchnage. Param will be maintained as a
* wrapper for one point release (at which time it will be deprecated).
* </p>
*/
@SuppressWarnings("unchecked")
public static class Param extends Parameter {
/**
* Provides support for text representations
*
* <p>
* The parameter type of String is assumed.
* </p>
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
*/
public Param(String key) {
this(key, String.class, null);
}
/**
* Provides support for text representations.
*
* <p>
* You may specify a <code>type</code> for this Param.
* </p>
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
*/
public Param(String key, Class<?> type) {
this(key, type, null);
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
*/
public Param(String key, Class<?> type, String description) {
this(key, type, description, true);
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
* @param required <code>true</code> is param is required
*/
public Param(String key, Class<?> type, String description, boolean required) {
this(key, type, description, required, null);
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
* @param required <code>true</code> is param is required
* @param sample Sample value as an example for user input
*/
public Param(String key, Class<?> type, String description, boolean required, Object sample) {
this(key, type, description == null? null : new SimpleInternationalString(description),
required, sample, null);
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
* @param required <code>true</code> is param is required
* @param sample Sample value as an example for user input
*/
public Param(String key,
Class type,
InternationalString description,
boolean required,
Object sample) {
super(key, type, new SimpleInternationalString(key), description, required, 1, 1, sample, null);
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
* @param required <code>true</code> is param is required
* @param sample Sample value as an example for user input
* @param extra metadata information, preferably keyed by known identifiers
* like {@link Parameter#IS_PASSWORD}
*/
public Param(String key,
Class type,
String description,
boolean required,
Object sample,
Map<String, ?> metadata) {
this(key, type, new SimpleInternationalString(description), required, sample, metadata);
}
public Param(String key,
Class type,
String description,
boolean required,
Object sample,
Object... metadata ) {
this( key, type, description, required, sample, new KVP( metadata ));
}
/**
* Provides support for text representations
*
* @param key Key used to file this Param in the Parameter Map for
* createDataStore
* @param type Class type intended for this Param
* @param description User description of Param (40 chars or less)
* @param required <code>true</code> is param is required
* @param sample Sample value as an example for user input
* @param extra metadata information, preferably keyed by known identifiers
* like {@link Parameter#IS_PASSWORD}
*/
public Param(String key,
Class type,
InternationalString description,
boolean required,
Object sample,
Map<String, ?> metadata) {
super(key, type, new SimpleInternationalString(key), description, required, 1, 1, sample, metadata);
}
/**
* Lookup Param in a user supplied map.
*
* <p>
* Type conversion will occur if required, this may result in an
* IOException. An IOException will be throw in the Param is required
* and the Map does not contain the Map.
* </p>
*
* <p>
* The handle method is used to process the user's value.
* </p>
*
* @param map Map of user input
*
* @return Parameter as specified in map
*
* @throws IOException if parse could not handle value
*/
public Object lookUp(Map<String, ?> map) throws IOException {
if (!map.containsKey(key)) {
if (required) {
throw new IOException("Parameter " + key + " is required:" + description);
} else {
return null;
}
}
Object value = map.get(key);
if (value == null) {
return null;
}
if (value instanceof String && (type != String.class)) {
value = handle((String) value);
}
if (value == null) {
return null;
}
if (!type.isInstance(value)) {
throw new IOException(type.getName() + " required for parameter " + key + ": not "
+ value.getClass().getName());
}
return value;
}
/**
* Convert value to text representation for this Parameter
*
* @param value DOCUMENT ME!
*
* @return DOCUMENT ME!
*/
public String text(Object value) {
return value.toString();
}
/**
* Handle text in a sensible manner.
*
* <p>
* Performs the most common way of handling text value:
* </p>
*
* <ul>
* <li>
* null: If text is null
* </li>
* <li>
* origional text: if type == String.class
* </li>
* <li>
* null: if type != String.class and text.getLength == 0
* </li>
* <li>
* parse( text ): if type != String.class
* </li>
* </ul>
*
*
* @param text
*
* @return Value as processed by text
*
* @throws IOException If text could not be parsed
*/
public Object handle(String text) throws IOException {
if (text == null) {
return null;
}
if (type == String.class) {
return text;
}
if (text.length() == 0) {
return null;
}
// if type is an array, tokenize the string and have the reflection
// parsing be tried on each element, then build the array as a result
if (type.isArray()) {
StringTokenizer tokenizer = new StringTokenizer(text, " ");
List<Object> result = new ArrayList<Object>();
while (tokenizer.hasMoreTokens()) {
String token = tokenizer.nextToken();
Object element;
try {
if (type.getComponentType() == String.class) {
element = token;
} else {
element = parse(token);
}
} catch (IOException ioException) {
throw ioException;
} catch (Throwable throwable) {
throw new DataSourceException("Problem creating " + type.getName()
+ " from '" + text + "'", throwable);
}
result.add(element);
}
Object array = Array.newInstance(type.getComponentType(), result.size());
for (int i = 0; i < result.size(); i++) {
Array.set(array, i, result.get(i));
}
return array;
}
try {
return parse(text);
} catch (IOException ioException) {
throw ioException;
} catch (Throwable throwable) {
throw new DataSourceException("Problem creating " + type.getName() + " from '"
+ text + "'", throwable);
}
}
/**
* Provides support for text representations
*
* <p>
* Provides basic support for common types using reflection.
* </p>
*
* <p>
* If needed you may extend this class to handle your own custome
* types.
* </p>
*
* @param text Text representation of type should not be null or empty
*
* @return Object converted from text representation
*
* @throws Throwable DOCUMENT ME!
* @throws IOException If text could not be parsed
* @throws DataSourceException DOCUMENT ME!
*/
public Object parse(String text) throws Throwable {
Constructor<?> constructor;
try {
constructor = type.getConstructor(new Class[] { String.class });
} catch (SecurityException e) {
// type( String ) constructor is not public
throw new IOException("Could not create " + type.getName() + " from text");
} catch (NoSuchMethodException e) {
// No type( String ) constructor
throw new IOException("Could not create " + type.getName() + " from text");
}
try {
return constructor.newInstance(new Object[] { text, });
} catch (IllegalArgumentException illegalArgumentException) {
throw new DataSourceException("Could not create " + type.getName() + ": from '"
+ text + "'", illegalArgumentException);
} catch (InstantiationException instantiaionException) {
throw new DataSourceException("Could not create " + type.getName() + ": from '"
+ text + "'", instantiaionException);
} catch (IllegalAccessException illegalAccessException) {
throw new DataSourceException("Could not create " + type.getName() + ": from '"
+ text + "'", illegalAccessException);
} catch (InvocationTargetException targetException) {
throw targetException.getCause();
}
}
/**
* key=Type description
*/
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(key);
buf.append('=');
buf.append(type.getName());
buf.append(' ');
if (required) {
buf.append("REQUIRED ");
}
buf.append(description);
return buf.toString();
}
}
}