001: /*
002: * GeoTools - 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: package org.geotools.data;
017:
018: import java.io.IOException;
019: import java.lang.reflect.Array;
020: import java.lang.reflect.Constructor;
021: import java.lang.reflect.InvocationTargetException;
022: import java.util.ArrayList;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.StringTokenizer;
026: import org.geotools.factory.Factory;
027:
028: /**
029: * Constructs a live DataStore from a set of parameters.
030: *
031: * <p>
032: * An instance of this interface should exist for all data stores which want to
033: * take advantage of the dynamic plugin system. In addition to implementing
034: * this interface datastores should have a services file:
035: * </p>
036: *
037: * <p>
038: * <code>META-INF/services/org.geotools.data.DataStoreFactorySpi</code>
039: * </p>
040: *
041: * <p>
042: * The file should contain a single line which gives the full name of the
043: * implementing class.
044: * </p>
045: *
046: * <p>
047: * example:<br/><code>e.g.
048: * org.geotools.data.mytype.MyTypeDataSourceFacotry</code>
049: * </p>
050: *
051: * <p>
052: * The factories are never called directly by client code, instead the
053: * DataStoreFinder class is used.
054: * </p>
055: *
056: * <p>
057: * The following example shows how a user might connect to a PostGIS database,
058: * and maintain the resulting datastore in a registry:
059: * </p>
060: *
061: * <p>
062: * <pre><code>
063: * HashMap params = new HashMap();
064: * params.put("namespace", "leeds");
065: * params.put("dbtype", "postgis");
066: * params.put("host","feathers.leeds.ac.uk");
067: * params.put("port", "5432");
068: * params.put("database","postgis_test");
069: * params.put("user","postgis_ro");
070: * params.put("passwd","postgis_ro");
071: *
072: * DefaultRegistry registry = new DefaultRegistry();
073: * registry.addDataStore("leeds", params);
074: *
075: * DataStore postgis = registry.getDataStore( "leeds" );
076: * FeatureSource = postgis.getFeatureSource( "table" );
077: * </code></pre>
078: * </p>
079: * <h2>
080: *
081: * <ul>
082: * <li>
083: * Jody - can we please get something better then Param to describe what is
084: * allowed? <br>
085: * Jody - ISO19119 has something that looks okay, WSDL/SOAP could be used?
086: * </li>
087: * <li>
088: * Jody - can we seperate out Identification of a Service from configration of
089: * the service? <br>
090: * Jody - this is mostly a problem when managing user supplied configurations
091: * in GeoServer and uDig. <br>
092: * Jody - the "Catalog API" has now been ported and contains a URI as
093: * indentification, while still allowing configuration using a Map of
094: * parameters
095: * </li>
096: * </ul>
097: *
098: *
099: * @author Jody Garnett, Refractions Research
100: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/api/src/main/java/org/geotools/data/DataStoreFactorySpi.java $
101: */
102: public interface DataStoreFactorySpi extends Factory {
103: /**
104: * Construct a live data source using the params specifed.
105: *
106: * <p>
107: * You can think of this as setting up a connection to the back end data
108: * source.
109: * </p>
110: *
111: * <p>
112: * Magic Params: the following params are magic and are honoured by
113: * convention by the GeoServer and uDig application.
114: *
115: * <ul>
116: * <li>
117: * "user": is taken to be the user name
118: * </li>
119: * <li>
120: * "passwd": is taken to be the password
121: * </li>
122: * <li>
123: * "namespace": is taken to be the namespace prefix (and will be kept in
124: * sync with GeoServer namespace management.
125: * </li>
126: * </ul>
127: *
128: * When we eventually move over to the use of OpperationalParam we will
129: * have to find someway to codify this convention.
130: * </p>
131: *
132: * @param params The full set of information needed to construct a live
133: * data store. Typical key values for the map include: url -
134: * location of a resource, used by file reading datasources. dbtype
135: * - the type of the database to connect to, e.g. postgis, mysql
136: *
137: * @return The created DataStore, this may be null if the required resource
138: * was not found or if insufficent parameters were given. Note
139: * that canProcess() should have returned false if the problem is
140: * to do with insuficent parameters.
141: *
142: * @throws IOException if there were any problems setting up (creating or
143: * connecting) the datasource.
144: */
145: DataStore createDataStore(Map params) throws IOException;
146:
147: // /**
148: // * Construct a simple MetadataEntity providing internationlization information
149: // * for the data source that *would* be created by createDataStore.
150: // * <p>
151: // * Suitable for use by CatalogEntry, unknown if this will make
152: // * a DataStore behind the scenes or not. It is possible it will
153: // * communicate with the data source though (hense the IOException).
154: // * </p>
155: // * @param params The full set of information needed to construct a live
156: // * data store
157: // * @return MetadataEntity with descriptive information (including
158: // * internationlization support).
159: // * @throws IOException
160: // */
161: // DataSourceMetadataEnity createMetadata( Map params ) throws IOException;
162: DataStore createNewDataStore(Map params) throws IOException;
163:
164: /**
165: * Name suitable for display to end user.
166: *
167: * <p>
168: * A non localized display name for this data store type.
169: * </p>
170: *
171: * @return A short name suitable for display in a user interface.
172: */
173: String getDisplayName();
174:
175: /**
176: * Describe the nature of the datasource constructed by this factory.
177: *
178: * <p>
179: * A non localized description of this data store type.
180: * </p>
181: *
182: * @return A human readable description that is suitable for inclusion in a
183: * list of available datasources.
184: */
185: String getDescription();
186:
187: /**
188: * MetaData about the required Parameters (for createDataStore).
189: *
190: * <p>
191: * Interpretation of FeatureDescriptor values:
192: * </p>
193: *
194: * <ul>
195: * <li>
196: * getDisplayName(): Gets the localized display name of this feature.
197: * </li>
198: * <li>
199: * getName(): Gets the programmatic name of this feature (used as the key
200: * in params)
201: * </li>
202: * <li>
203: * getShortDescription(): Gets the short description of this feature.
204: * </li>
205: * </ul>
206: *
207: * <p>
208: * This should be the same as:
209: * </p>
210: * <pre><code>
211: * Object params = factory.getParameters();
212: * BeanInfo info = getBeanInfo( params );
213: *
214: * return info.getPropertyDescriptors();
215: * <code></pre>
216: *
217: * @return Param array describing the Map for createDataStore
218: */
219: Param[] getParametersInfo();
220:
221: /**
222: * Test to see if this factory is suitable for processing the data pointed
223: * to by the params map.
224: *
225: * <p>
226: * If this datasource requires a number of parameters then this mehtod
227: * should check that they are all present and that they are all valid. If
228: * the datasource is a file reading data source then the extentions or
229: * mime types of any files specified should be checked. For example, a
230: * Shapefile datasource should check that the url param ends with shp,
231: * such tests should be case insensative.
232: * </p>
233: *
234: * @param params The full set of information needed to construct a live
235: * data source.
236: *
237: * @return booean true if and only if this factory can process the resource
238: * indicated by the param set and all the required params are
239: * pressent.
240: */
241: boolean canProcess(java.util.Map params);
242:
243: /**
244: * Test to see if this datastore is available, if it has all the
245: * appropriate libraries to construct a datastore. Most datastores should
246: * return true, because geotools will distribute the appropriate
247: * libraries. Though it's not a bad idea for DataStoreFactories to check
248: * to make sure that the libraries are there. OracleDataStoreFactory is
249: * an example of one that may generally return false, since geotools can
250: * not distribute the oracle jars, they must be added by the client. One
251: * may ask how this is different than canProcess, and basically available
252: * is used by the DataStoreFinder getAvailableDataStore method, so that
253: * DataStores that can not even be used do not show up as options in gui
254: * applications.
255: *
256: * @return <tt>true</tt> if and only if this factory has all the
257: * appropriate jars on the classpath to create DataStores.
258: */
259: boolean isAvailable();
260:
261: /**
262: * Data class used to capture Parameter requirements.
263: *
264: * <p>
265: * Subclasses may provide specific setAsText()/getAsText() requirements
266: * </p>
267: *
268: * <p>
269: * Warning: We would like to start moving towards a common paraemters
270: * framework with GridCoverageExchnage. Param will be maintained as a
271: * wrapper for one point release (at which time it will be deprecated).
272: * </p>
273: */
274: class Param {
275: /** True if Param is required */
276: final public boolean required;
277:
278: /** Key used in Parameter map */
279: final public String key;
280:
281: /** Type of information required */
282: final public Class type;
283:
284: /** Short description (less then 40 characters) */
285: final public String description;
286:
287: /**
288: * Sampel value provided as an example for user input.
289: *
290: * <p>
291: * May be passed to getAsText( sample ) for inital text based user
292: * interface default.
293: * </p>
294: */
295: final public Object sample;
296:
297: /**
298: * Provides support for text representations
299: *
300: * <p>
301: * The parameter type of String is assumed.
302: * </p>
303: *
304: * @param key Key used to file this Param in the Parameter Map for
305: * createDataStore
306: */
307: public Param(String key) {
308: this (key, String.class, null);
309: }
310:
311: /**
312: * Provides support for text representations.
313: *
314: * <p>
315: * You may specify a <code>type</code> for this Param.
316: * </p>
317: *
318: * @param key Key used to file this Param in the Parameter Map for
319: * createDataStore
320: * @param type Class type intended for this Param
321: */
322: public Param(String key, Class type) {
323: this (key, type, null);
324: }
325:
326: /**
327: * Provides support for text representations
328: *
329: * @param key Key used to file this Param in the Parameter Map for
330: * createDataStore
331: * @param type Class type intended for this Param
332: * @param description User description of Param (40 chars or less)
333: */
334: public Param(String key, Class type, String description) {
335: this (key, type, description, true);
336: }
337:
338: /**
339: * Provides support for text representations
340: *
341: * @param key Key used to file this Param in the Parameter Map for
342: * createDataStore
343: * @param type Class type intended for this Param
344: * @param description User description of Param (40 chars or less)
345: * @param required <code>true</code> is param is required
346: */
347: public Param(String key, Class type, String description,
348: boolean required) {
349: this (key, type, description, required, null);
350: }
351:
352: /**
353: * Provides support for text representations
354: *
355: * @param key Key used to file this Param in the Parameter Map for
356: * createDataStore
357: * @param type Class type intended for this Param
358: * @param description User description of Param (40 chars or less)
359: * @param required <code>true</code> is param is required
360: * @param sample Sample value as an example for user input
361: */
362: public Param(String key, Class type, String description,
363: boolean required, Object sample) {
364: this .key = key;
365: this .type = type;
366: this .description = description;
367: this .required = required;
368: this .sample = sample;
369: }
370:
371: /**
372: * Lookup Param in a user supplied map.
373: *
374: * <p>
375: * Type conversion will occur if required, this may result in an
376: * IOException. An IOException will be throw in the Param is required
377: * and the Map does not contain the Map.
378: * </p>
379: *
380: * <p>
381: * The handle method is used to process the user's value.
382: * </p>
383: *
384: * @param map Map of user input
385: *
386: * @return Parameter as specified in map
387: *
388: * @throws IOException if parse could not handle value
389: */
390: public Object lookUp(Map map) throws IOException {
391: if (!map.containsKey(key)) {
392: if (required) {
393: throw new IOException("Parameter " + key
394: + " is required:" + description);
395: } else {
396: return null;
397: }
398: }
399:
400: Object value = map.get(key);
401:
402: if (value == null) {
403: return null;
404: }
405:
406: if (value instanceof String && (type != String.class)) {
407: value = handle((String) value);
408: }
409:
410: if (value == null) {
411: return null;
412: }
413:
414: if (!type.isInstance(value)) {
415: throw new IOException(type.getName()
416: + " required for parameter " + key + ": not "
417: + value.getClass().getName());
418: }
419:
420: return value;
421: }
422:
423: /**
424: * Convert value to text representation for this Parameter
425: *
426: * @param value DOCUMENT ME!
427: *
428: * @return DOCUMENT ME!
429: */
430: public String text(Object value) {
431: return value.toString();
432: }
433:
434: /**
435: * Handle text in a sensible manner.
436: *
437: * <p>
438: * Performs the most common way of handling text value:
439: * </p>
440: *
441: * <ul>
442: * <li>
443: * null: If text is null
444: * </li>
445: * <li>
446: * origional text: if type == String.class
447: * </li>
448: * <li>
449: * null: if type != String.class and text.getLength == 0
450: * </li>
451: * <li>
452: * parse( text ): if type != String.class
453: * </li>
454: * </ul>
455: *
456: *
457: * @param text
458: *
459: * @return Value as processed by text
460: *
461: * @throws IOException If text could not be parsed
462: * @throws DataSourceException DOCUMENT ME!
463: */
464: public Object handle(String text) throws IOException {
465: if (text == null) {
466: return null;
467: }
468:
469: if (type == String.class) {
470: return text;
471: }
472:
473: if (text.length() == 0) {
474: return null;
475: }
476:
477: // if type is an array, tokenize the string and have the reflection
478: // parsing be tried on each element, then build the array as a result
479: if (type.isArray()) {
480: StringTokenizer tokenizer = new StringTokenizer(text,
481: " ");
482: List result = new ArrayList();
483:
484: while (tokenizer.hasMoreTokens()) {
485: String token = tokenizer.nextToken();
486: Object element;
487:
488: try {
489: if (type.getComponentType() == String.class) {
490: element = token;
491: } else {
492: element = parse(token);
493: }
494: } catch (IOException ioException) {
495: throw ioException;
496: } catch (Throwable throwable) {
497: throw new DataSourceException(
498: "Problem creating " + type.getName()
499: + " from '" + text + "'",
500: throwable);
501: }
502:
503: result.add(element);
504: }
505:
506: Object array = Array.newInstance(type
507: .getComponentType(), result.size());
508:
509: for (int i = 0; i < result.size(); i++) {
510: Array.set(array, i, result.get(i));
511: }
512:
513: return array;
514: }
515:
516: try {
517: return parse(text);
518: } catch (IOException ioException) {
519: throw ioException;
520: } catch (Throwable throwable) {
521: throw new DataSourceException("Problem creating "
522: + type.getName() + " from '" + text + "'",
523: throwable);
524: }
525: }
526:
527: /**
528: * Provides support for text representations
529: *
530: * <p>
531: * Provides basic support for common types using reflection.
532: * </p>
533: *
534: * <p>
535: * If needed you may extend this class to handle your own custome
536: * types.
537: * </p>
538: *
539: * @param text Text representation of type should not be null or empty
540: *
541: * @return Object converted from text representation
542: *
543: * @throws Throwable DOCUMENT ME!
544: * @throws IOException If text could not be parsed
545: * @throws DataSourceException DOCUMENT ME!
546: */
547: public Object parse(String text) throws Throwable {
548: Constructor constructor;
549:
550: try {
551: constructor = type
552: .getConstructor(new Class[] { String.class });
553: } catch (SecurityException e) {
554: // type( String ) constructor is not public
555: throw new IOException("Could not create "
556: + type.getName() + " from text");
557: } catch (NoSuchMethodException e) {
558: // No type( String ) constructor
559: throw new IOException("Could not create "
560: + type.getName() + " from text");
561: }
562:
563: try {
564: return constructor.newInstance(new Object[] { text, });
565: } catch (IllegalArgumentException illegalArgumentException) {
566: throw new DataSourceException("Could not create "
567: + type.getName() + ": from '" + text + "'",
568: illegalArgumentException);
569: } catch (InstantiationException instantiaionException) {
570: throw new DataSourceException("Could not create "
571: + type.getName() + ": from '" + text + "'",
572: instantiaionException);
573: } catch (IllegalAccessException illegalAccessException) {
574: throw new DataSourceException("Could not create "
575: + type.getName() + ": from '" + text + "'",
576: illegalAccessException);
577: } catch (InvocationTargetException targetException) {
578: throw targetException.getCause();
579: }
580: }
581:
582: /**
583: * key=Type description
584: */
585: public String toString() {
586: StringBuffer buf = new StringBuffer();
587: buf.append(key);
588: buf.append('=');
589: buf.append(type.getName());
590: buf.append(' ');
591:
592: if (required) {
593: buf.append("REQUIRED ");
594: }
595:
596: buf.append(description);
597:
598: return buf.toString();
599: }
600: }
601: }
|