001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-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.util.Collection;
020: import java.util.Collections;
021: import java.util.Map;
022: import java.util.Set;
023: import java.util.logging.Level;
024: import java.util.logging.Logger;
025:
026: import org.geotools.data.view.DefaultView;
027: import org.geotools.feature.FeatureType;
028: import org.geotools.feature.SchemaException;
029: import org.opengis.filter.Filter;
030:
031: import com.vividsolutions.jts.geom.Envelope;
032:
033: /**
034: * Represents a stating point for implementing your own DataStore.
035: *
036: * <p>
037: * The goal is to have this class provide <b>everything</b> else if you can
038: * only provide:
039: * </p>
040: *
041: * <ul>
042: * <li>
043: * String[] getFeatureTypes()
044: * </li>
045: * <li>
046: * FeatureType getSchema(String typeName)
047: * </li>
048: * <li>
049: * FeatureReader getFeatureReader( typeName )
050: * </li>
051: * <li>
052: * FeatureWriter getFeatureWriter( typeName )
053: * </li>
054: * </ul>
055: *
056: * and optionally this protected methods to allow custom query optimizations:
057: *
058: * <ul>
059: * <li>
060: * Filter getUnsupportedFilter(String typeName, Filter filter)
061: * </li>
062: * <li>
063: * FeatureReader getFeatureReader(String typeName, Query query)
064: * </li>
065: * </ul>
066: *
067: * <p>
068: * All remaining functionality is implemented against these methods, including
069: * Transaction and Locking Support. These implementations will not be optimal
070: * but they will work.
071: * </p>
072: *
073: * <p>
074: * Pleae note that there may be a better place for you to start out from, (like
075: * JDBCDataStore).
076: * </p>
077: *
078: * @author jgarnett
079: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/AbstractDataStore.java $
080: */
081: public abstract class AbstractDataStore implements DataStore {
082: /** The logger for the filter module. */
083: protected static final Logger LOGGER = org.geotools.util.logging.Logging
084: .getLogger("org.geotools.data");
085:
086: /** Manages listener lists for FeatureSource implementation */
087: public FeatureListenerManager listenerManager = new FeatureListenerManager();
088:
089: /**
090: * Flags AbstractDataStore to allow Modification.
091: * <p>
092: * GetFeatureSource will return a FeatureStore is this is true.
093: * </p>
094: */
095: protected final boolean isWriteable;
096:
097: /**
098: * Manages InProcess locks for FeatureLocking implementations.
099: *
100: * <p>
101: * May be null if subclass is providing real locking.
102: * </p>
103: */
104: private InProcessLockingManager lockingManager;
105:
106: /** Default (Writeable) DataStore */
107: public AbstractDataStore() {
108: this (true);
109: }
110:
111: /**
112: * AbstractDataStore creation.
113: *
114: * @param isWriteable true for writeable DataStore.
115: */
116: public AbstractDataStore(boolean isWriteable) {
117: this .isWriteable = isWriteable;
118: lockingManager = createLockingManager();
119: }
120:
121: /**
122: * Currently returns an InProcessLockingManager.
123: *
124: * <p>
125: * Subclasses that implement real locking may override this method to
126: * return <code>null</code>.
127: * </p>
128: *
129: * @return InProcessLockingManager or null.
130: */
131: protected InProcessLockingManager createLockingManager() {
132: return new InProcessLockingManager();
133: }
134:
135: // public void fireAdded( Feature newFeature ){
136: // String typeName = newFeature.getFeatureType().getTypeName();
137: // listenerManager.fireFeaturesAdded( typeName, Transaction.AUTO_COMMIT, newFeature.getBounds(), false );
138: // }
139: // public void fireRemoved( Feature removedFeature ){
140: // String typeName = removedFeature.getFeatureType().getTypeName();
141: // listenerManager.fireFeaturesRemoved( typeName, Transaction.AUTO_COMMIT, removedFeature.getBounds(), false );
142: // }
143: // public void fireChanged( Feature before, Feature after ){
144: // String typeName = after.getFeatureType().getTypeName();
145: // Envelope bounds = new Envelope();
146: // bounds.expandToInclude( before.getBounds() );
147: // bounds.expandToInclude( after.getBounds() );
148: // listenerManager.fireFeaturesChanged( typeName, Transaction.AUTO_COMMIT, bounds, false );
149: // }
150:
151: /**
152: * Subclass override to provide access to metadata.
153: * <p>
154: * CreateTypeEntry uses this method to aquire metadata information,
155: * if available.
156: * </p>
157: */
158: protected Map createMetadata(String typeName) {
159: return Collections.EMPTY_MAP;
160: }
161:
162: /** Convience method for retriving all the names from the Catalog Entires */
163: public abstract String[] getTypeNames() throws IOException;
164:
165: /** Retrive schema information for typeName */
166: public abstract FeatureType getSchema(String typeName)
167: throws IOException;
168:
169: /**
170: * Subclass must implement.
171: *
172: * @param typeName
173: *
174: * @return FeatureReader over contents of typeName
175: *
176: */
177: protected abstract FeatureReader getFeatureReader(String typeName)
178: throws IOException;
179:
180: /**
181: * Subclass can implement this to provide writing support.
182: *
183: * @param typeName
184: *
185: * @return FeatureWriter over contents of typeName
186: * @throws IOException
187: *
188: * @throws IOException Subclass may throw IOException
189: * @throws UnsupportedOperationException Subclass may implement
190: * @deprecated
191: */
192: protected FeatureWriter getFeatureWriter(String typeName)
193: throws IOException {
194: throw new UnsupportedOperationException(
195: "Schema creation not supported");
196: }
197:
198: /**
199: * Subclass should implement this to provide writing support.
200: * <p>A feature writer writes to the resource so it should considered to always be committing.
201: * The transaction is passed in so that it can be known what FeatureListeners should be notified of the
202: * changes. If the Transaction is AUTOCOMMIT then all listeners should be notified. If not
203: * all listeners that are NOT registered with that transaction should be notified.<p>
204: * @param typeName
205: * @param transaction a feature writer
206: * @return FeatureWriter over contents of typeName
207: * @throws IOException
208: *
209: * @throws IOException Subclass may throw IOException
210: * @throws UnsupportedOperationException Subclass may implement
211: */
212: protected FeatureWriter createFeatureWriter(String typeName,
213: Transaction transaction) throws IOException {
214: throw new UnsupportedOperationException(
215: "Schema creation not supported");
216: }
217:
218: /**
219: * Subclass should implement to provide writing support.
220: *
221: * @param featureType Requested FeatureType
222: * @throws IOException
223: *
224: * @throws IOException Subclass may throw IOException
225: * @throws UnsupportedOperationException Subclass may implement
226: */
227: public void createSchema(FeatureType featureType)
228: throws IOException {
229: throw new UnsupportedOperationException(
230: "Schema creation not supported");
231: }
232:
233: /* (non-Javadoc)
234: * @see org.geotools.data.DataStore#updateSchema(java.lang.String, org.geotools.feature.FeatureType)
235: */
236: public void updateSchema(String typeName, FeatureType featureType) {
237: throw new UnsupportedOperationException(
238: "Schema modification not supported");
239: }
240:
241: // Jody - This is my recomendation for DataStore
242: // in order to support CS reprojection and override
243: public FeatureSource getView(final Query query) throws IOException,
244: SchemaException {
245: return new DefaultView(this .getFeatureSource(query
246: .getTypeName()), query);
247: }
248:
249: /**
250: * Default implementation based on getFeatureReader and getFeatureWriter.
251: *
252: * <p>
253: * We should be able to optimize this to only get the RowSet once
254: * </p>
255: *
256: * @see org.geotools.data.DataStore#getFeatureSource(java.lang.String)
257: */
258: public FeatureSource getFeatureSource(final String typeName)
259: throws IOException {
260: final FeatureType featureType = getSchema(typeName);
261:
262: if (isWriteable) {
263: if (lockingManager != null)
264: return new AbstractFeatureLocking(getSupportedHints()) {
265: public DataStore getDataStore() {
266: return AbstractDataStore.this ;
267: }
268:
269: public void addFeatureListener(
270: FeatureListener listener) {
271: listenerManager.addFeatureListener(this ,
272: listener);
273: }
274:
275: public void removeFeatureListener(
276: FeatureListener listener) {
277: listenerManager.removeFeatureListener(this ,
278: listener);
279: }
280:
281: public FeatureType getSchema() {
282: return featureType;
283: }
284: };
285: return new AbstractFeatureStore(getSupportedHints()) {
286: public DataStore getDataStore() {
287: return AbstractDataStore.this ;
288: }
289:
290: public void addFeatureListener(FeatureListener listener) {
291: listenerManager.addFeatureListener(this , listener);
292: }
293:
294: public void removeFeatureListener(
295: FeatureListener listener) {
296: listenerManager.removeFeatureListener(this ,
297: listener);
298: }
299:
300: public FeatureType getSchema() {
301: return featureType;
302: }
303: };
304: }
305: return new AbstractFeatureSource(getSupportedHints()) {
306: public DataStore getDataStore() {
307: return AbstractDataStore.this ;
308: }
309:
310: public void addFeatureListener(FeatureListener listener) {
311: listenerManager.addFeatureListener(this , listener);
312: }
313:
314: public void removeFeatureListener(FeatureListener listener) {
315: listenerManager.removeFeatureListener(this , listener);
316: }
317:
318: public FeatureType getSchema() {
319: return featureType;
320: }
321: };
322: }
323:
324: // Jody - Recomend moving to the following
325: // When we are ready for CoordinateSystem support
326: public FeatureReader getFeatureReader(Query query,
327: Transaction transaction) throws IOException {
328: Filter filter = query.getFilter();
329: String typeName = query.getTypeName();
330: String propertyNames[] = query.getPropertyNames();
331:
332: if (filter == null) {
333: throw new NullPointerException(
334: "getFeatureReader requires Filter: "
335: + "did you mean Filter.INCLUDE?");
336: }
337: if (typeName == null) {
338: throw new NullPointerException(
339: "getFeatureReader requires typeName: "
340: + "use getTypeNames() for a list of available types");
341: }
342: if (transaction == null) {
343: throw new NullPointerException(
344: "getFeatureReader requires Transaction: "
345: + "did you mean to use Transaction.AUTO_COMMIT?");
346: }
347: FeatureType featureType = getSchema(query.getTypeName());
348:
349: if (propertyNames != null
350: || query.getCoordinateSystem() != null) {
351: try {
352: featureType = DataUtilities.createSubType(featureType,
353: propertyNames, query.getCoordinateSystem());
354: } catch (SchemaException e) {
355: LOGGER.log(Level.FINEST, e.getMessage(), e);
356: throw new DataSourceException(
357: "Could not create Feature Type for query", e);
358:
359: }
360: }
361: if (filter == Filter.EXCLUDE || filter.equals(Filter.EXCLUDE)) {
362: return new EmptyFeatureReader(featureType);
363: }
364: //GR: allow subclases to implement as much filtering as they can,
365: //by returning just it's unsupperted filter
366: filter = getUnsupportedFilter(typeName, filter);
367: if (filter == null) {
368: throw new NullPointerException(
369: "getUnsupportedFilter shouldn't return null. Do you mean Filter.INCLUDE?");
370: }
371:
372: // There are cases where the readers have to lock. Take shapefile for example. Getting a Reader causes
373: // the file to be locked. However on a commit TransactionStateDiff locks before a writer is obtained. In order to
374: // prevent deadlocks either the diff has to obtained first or the reader has to be obtained first.
375: // Because shapefile writes to a buffer first the actual write lock is not flipped until the transaction has most of the work
376: // done. As a result I suggest getting the diff first then getting the reader.
377: // JE
378: Diff diff = null;
379: if (transaction != Transaction.AUTO_COMMIT) {
380: diff = state(transaction).diff(typeName);
381: }
382:
383: // This calls our subclass "simple" implementation
384: // All other functionality will be built as a reader around
385: // this class
386: //
387: FeatureReader reader = getFeatureReader(typeName, query);
388:
389: if (diff != null)
390: reader = new DiffFeatureReader(reader, diff, query
391: .getFilter());
392:
393: if (!filter.equals(Filter.INCLUDE)) {
394: reader = new FilteringFeatureReader(reader, filter);
395: }
396:
397: if (!featureType.equals(reader.getFeatureType())) {
398: LOGGER
399: .fine("Recasting feature type to subtype by using a ReTypeFeatureReader");
400: reader = new ReTypeFeatureReader(reader, featureType, false);
401: }
402:
403: if (query.getMaxFeatures() != Query.DEFAULT_MAX) {
404: reader = new MaxFeatureReader(reader, query
405: .getMaxFeatures());
406: }
407:
408: return reader;
409: }
410:
411: /**
412: * GR: this method is called from inside getFeatureReader(Query ,Transaction )
413: * to allow subclasses return an optimized FeatureReader wich supports the
414: * filter and attributes truncation specified in <code>query</code>
415: * <p>
416: * A subclass that supports the creation of such an optimized FeatureReader
417: * shold override this method. Otherwise, it just returns
418: * <code>getFeatureReader(typeName)</code>
419: * <p>
420: */
421: protected FeatureReader getFeatureReader(String typeName,
422: Query query) throws IOException {
423: return getFeatureReader(typeName);
424: }
425:
426: /**
427: * GR: if a subclass supports filtering, it should override this method
428: * to return the unsupported part of the passed filter, so a
429: * FilteringFeatureReader will be constructed upon it. Otherwise it will
430: * just return the same filter.
431: * <p>
432: * If the complete filter is supported, the subclass must return <code>Filter.INCLUDE</code>
433: * </p>
434: */
435: protected Filter getUnsupportedFilter(String typeName, Filter filter) {
436: return filter;
437: }
438:
439: /*
440: * @see org.geotools.data.DataStore#getFeatureReader(org.geotools.feature.FeatureType,
441: * org.geotools.filter.Filter, org.geotools.data.Transaction)
442: *
443: public FeatureReader getFeatureReader(FeatureType featureType,
444: Filter filter, Transaction transaction) throws IOException {
445: if (filter == null) {
446: throw new NullPointerException("getFeatureReader requires Filter: "
447: + "did you mean Filter.INCLUDE?");
448: }
449:
450: if (featureType == null) {
451: throw new NullPointerException(
452: "getFeatureReader requires FeatureType: "
453: + "use getSchema( typeName ) to aquire a FeatureType");
454: }
455:
456: if (transaction == null) {
457: throw new NullPointerException(
458: "getFeatureReader requires Transaction: "
459: + "did you mean to use Transaction.AUTO_COMMIT?");
460: }
461:
462: if (filter == Filter.EXCLUDE) {
463: return new EmptyFeatureReader(featureType);
464: }
465:
466: String typeName = featureType.getTypeName();
467:
468: FeatureReader reader = getFeatureReader(typeName);
469:
470: if (filter != Filter.INCLUDE) {
471: reader = new FilteringFeatureReader(reader, filter);
472: }
473:
474: if (transaction != Transaction.AUTO_COMMIT) {
475: Map diff = state(transaction).diff(typeName);
476: reader = new DiffFeatureReader(reader, diff);
477: }
478:
479: if (!featureType.equals(reader.getFeatureType())) {
480: reader = new ReTypeFeatureReader(reader, featureType);
481: }
482:
483: return reader;
484: }
485: */
486: TransactionStateDiff state(Transaction transaction) {
487: synchronized (transaction) {
488: TransactionStateDiff state = (TransactionStateDiff) transaction
489: .getState(this );
490:
491: if (state == null) {
492: state = new TransactionStateDiff(this );
493: transaction.putState(this , state);
494: }
495:
496: return state;
497: }
498: }
499:
500: /* (non-Javadoc)
501: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String, org.geotools.filter.Filter, org.geotools.data.Transaction)
502: */
503: public FeatureWriter getFeatureWriter(String typeName,
504: Filter filter, Transaction transaction) throws IOException {
505: if (filter == null) {
506: throw new NullPointerException(
507: "getFeatureReader requires Filter: "
508: + "did you mean Filter.INCLUDE?");
509: }
510:
511: if (filter == Filter.EXCLUDE) {
512: FeatureType featureType = getSchema(typeName);
513:
514: return new EmptyFeatureWriter(featureType);
515: }
516:
517: if (transaction == null) {
518: throw new NullPointerException(
519: "getFeatureWriter requires Transaction: "
520: + "did you mean to use Transaction.AUTO_COMMIT?");
521: }
522:
523: FeatureWriter writer;
524:
525: if (transaction == Transaction.AUTO_COMMIT) {
526: try {
527: writer = createFeatureWriter(typeName, transaction);
528: } catch (UnsupportedOperationException e) {
529: // This is for backward compatibility.
530: writer = getFeatureWriter(typeName);
531: }
532: } else {
533: writer = state(transaction).writer(typeName, filter);
534: }
535:
536: if (lockingManager != null) {
537: // subclass has not provided locking so we will
538: // fake it with InProcess locks
539: writer = lockingManager.checkedWriter(writer, transaction);
540: }
541:
542: if (filter != Filter.INCLUDE) {
543: writer = new FilteringFeatureWriter(writer, filter);
544: }
545:
546: return writer;
547: }
548:
549: /* (non-Javadoc)
550: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String, org.geotools.data.Transaction)
551: */
552: public FeatureWriter getFeatureWriter(String typeName,
553: Transaction transaction) throws IOException {
554:
555: return getFeatureWriter(typeName, Filter.INCLUDE, transaction);
556: }
557:
558: /* (non-Javadoc)
559: * @see org.geotools.data.DataStore#getFeatureWriterAppend(java.lang.String, org.geotools.data.Transaction)
560: *
561: */
562: public FeatureWriter getFeatureWriterAppend(String typeName,
563: Transaction transaction) throws IOException {
564: FeatureWriter writer = getFeatureWriter(typeName, transaction);
565:
566: while (writer.hasNext()) {
567: writer.next(); // Hmmm this would be a use for skip() then?
568: }
569:
570: return writer;
571: }
572:
573: /**
574: * Locking manager used for this DataStore.
575: *
576: * <p>
577: * By default AbstractDataStore makes use of InProcessLockingManager.
578: * </p>
579: *
580: *
581: * @see org.geotools.data.DataStore#getLockingManager()
582: */
583: public LockingManager getLockingManager() {
584: return lockingManager;
585: }
586:
587: /**
588: * Computes the bounds of the features for the specified feature type that
589: * satisfy the query provided that there is a fast way to get that result.
590: * <p>
591: * Will return null if there is not fast way to compute the bounds. Since
592: * it's based on some kind of header/cached information, it's not guaranteed
593: * to be real bound of the features
594: * </p>
595: * @param query
596: * @return the bounds, or null if too expensive
597: * @throws SchemaNotFoundException
598: * @throws IOException
599: */
600: protected Envelope getBounds(Query query) throws IOException {
601: return null; // too expensive
602: }
603:
604: /**
605: * Gets the number of the features that would be returned by this query for
606: * the specified feature type.
607: * <p>
608: * If getBounds(Query) returns <code>-1</code> due to expense consider
609: * using <code>getFeatures(Query).getCount()</code> as a an alternative.
610: * </p>
611: *
612: * @param query Contains the Filter and MaxFeatures to find the bounds for.
613: * @return The number of Features provided by the Query or <code>-1</code>
614: * if count is too expensive to calculate or any errors or occur.
615: * @throws IOException
616: *
617: * @throws IOException if there are errors getting the count
618: */
619: protected int getCount(Query query) throws IOException {
620: return -1; // too expensive
621: }
622:
623: /**
624: * If you are using the automated FeatureSource/Store/Locking creation, this method
625: * allows for the specification of the supported hints.
626: * @return
627: */
628: protected Set getSupportedHints() {
629: return Collections.EMPTY_SET;
630: }
631:
632: /**
633: * Dummy implementation, it's a no-op. Subclasses holding to system resources must
634: * override this method and release them.
635: */
636: public void dispose() {
637: // nothing to do
638: }
639: }
|