001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2004-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.store;
017:
018: import java.io.IOException;
019: import java.util.Collections;
020: import java.util.Map;
021: import java.util.logging.Level;
022: import java.util.logging.Logger;
023:
024: import org.geotools.data.AbstractFeatureLocking;
025: import org.geotools.data.AbstractFeatureSource;
026: import org.geotools.data.AbstractFeatureStore;
027: import org.geotools.data.DataSourceException;
028: import org.geotools.data.DataStore;
029: import org.geotools.data.DataUtilities;
030: import org.geotools.data.Diff;
031: import org.geotools.data.DiffFeatureReader;
032: import org.geotools.data.EmptyFeatureReader;
033: import org.geotools.data.EmptyFeatureWriter;
034: import org.geotools.data.FeatureListener;
035: import org.geotools.data.FeatureListenerManager;
036: import org.geotools.data.FeatureLocking;
037: import org.geotools.data.FeatureReader;
038: import org.geotools.data.FeatureSource;
039: import org.geotools.data.FeatureStore;
040: import org.geotools.data.FeatureWriter;
041: import org.geotools.data.FilteringFeatureReader;
042: import org.geotools.data.InProcessLockingManager;
043: import org.geotools.data.MaxFeatureReader;
044: import org.geotools.data.Query;
045: import org.geotools.data.ReTypeFeatureReader;
046: import org.geotools.data.Transaction;
047: import org.geotools.data.collection.DelegateFeatureReader;
048: import org.geotools.feature.Feature;
049: import org.geotools.feature.FeatureCollection;
050: import org.geotools.feature.FeatureType;
051: import org.geotools.feature.SchemaException;
052: import org.geotools.feature.collection.DelegateFeatureIterator;
053: import org.opengis.filter.Filter;
054: import org.geotools.geometry.jts.JTS;
055: import org.geotools.util.SimpleInternationalString;
056: import org.opengis.referencing.crs.CoordinateReferenceSystem;
057: import org.opengis.util.InternationalString;
058:
059: import com.vividsolutions.jts.geom.Envelope;
060:
061: /**
062: * Starting place for holding information about a FeatureType.
063: * <p>
064: * Like say for instance the FeatureType, its metadata and so on.
065: * </p>
066: * <p>
067: * The default implemenation should contain enough information to wean
068: * us off of AbstractDataStore. That is it should provide its own locking
069: * and event notification.
070: * </p>
071: * <p>
072: * There is a naming convention:
073: * <ul>
074: * <li> data access follows bean conventions: getTypeName(), getSchema()
075: * <li> resource access methods follow Collections conventions reader(),
076: * writer(), etc...
077: * <li> overrrides are all protected and follow factory conventions:
078: * createWriter(), createAppend(), createFeatureSource(),
079: * createFeatureStore(), etc...
080: * </ul>
081: * <li>
082: * </p>
083: * <p>
084: * Feedback:
085: * <ul>
086: * <li>even notification yes
087: * <li>locking not - locking needs to be rejuggled
088: * <li>naming convention really helps when subclassing
089: * </ul>
090: * </p>
091: *
092: * @author jgarnett
093: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/main/src/main/java/org/geotools/data/store/ActiveTypeEntry.java $
094: */
095: public abstract class ActiveTypeEntry implements TypeEntry {
096: protected static final Logger LOGGER = org.geotools.util.logging.Logging
097: .getLogger("org.geotools.data.store");
098:
099: /**
100: * Remember parent.
101: * <p>
102: * We only refer to partent as a DataSource to keep hacks down.
103: * </p>
104: */
105: protected DataStore parent;
106: private final FeatureType schema;
107: private final Map metadata;
108: private FeatureListenerManager listeners = new FeatureListenerManager();
109:
110: /** Cached count */
111: int count;
112:
113: /** Cached bounds */
114: Envelope bounds;
115:
116: public ActiveTypeEntry(DataStore parent, FeatureType schema,
117: Map metadata) {
118: this .schema = schema;
119: this .metadata = metadata;
120: this .parent = parent;
121:
122: // TODO: Should listen to events and empty the cache when edits occur
123:
124: }
125:
126: /**
127: * TODO summary sentence for getDisplayName ...
128: *
129: * @see org.geotools.data.TypeEntry#getDisplayName()
130: */
131: public InternationalString getDisplayName() {
132: return new SimpleInternationalString(schema.getTypeName());
133: }
134:
135: /**
136: * TODO summary sentence for getDescription ...
137: *
138: * @see org.geotools.data.TypeEntry#getDescription()
139: */
140: public InternationalString getDescription() {
141: return null;
142: }
143:
144: /**
145: * TODO summary sentence for getFeatureType ...
146: *
147: * @see org.geotools.data.TypeEntry#getFeatureType()
148: * @throws IOException
149: */
150: public FeatureType getFeatureType() {
151: return schema;
152: }
153:
154: /**
155: * Bounding box for associated Feature Collection, will be calcualted as needed.
156: * <p>
157: * Note bounding box is returned in lat/long - the coordinate system of the default geometry
158: * is used to provide this reprojection.
159: * </p>
160: */
161: // public synchronized Envelope getBounds() {
162: // if( bounds != null ) {
163: // bounds = createBounds();
164: // }
165: // return bounds;
166: // }
167: /**
168: * Override to provide your own optimized calculation of bbox.
169: * <p>
170: * Default impelmenation uses the a feature source.
171: *
172: * @return BBox in lat long
173: */
174: // protected Envelope createBounds() {
175: // Envelope bbox;
176: // try {
177: // FeatureSource source = getFeatureSource();
178: // bbox = source.getBounds();
179: // if( bbox == null ){
180: // bbox = source.getFeatures().getBounds();
181: // }
182: // try {
183: // CoordinateReferenceSystem cs = source.getSchema().getDefaultGeometry().getCoordinateSystem();
184: // bbox = JTS.toGeographic(bbox,cs);
185: // }
186: // catch (Error badRepoject ) {
187: // badRepoject.printStackTrace();
188: // }
189: // } catch (Exception e) {
190: // bbox = new Envelope();
191: // }
192: // return bbox;
193: // }
194: /**
195: * TODO summary sentence for getCount ...
196: *
197: * @see org.geotools.data.TypeEntry#getCount()
198: */
199: // public int getCount() {
200: // if( count != -1 ) return count;
201: // try {
202: // FeatureSource source = getFeatureSource();
203: // count = source.getCount( Query.ALL );
204: // if( count == -1 ){
205: // count = source.getFeatures().size();
206: // }
207: // } catch (IOException e) {
208: // bounds = new Envelope();
209: // }
210: // return count;
211: // }
212: /**
213: * Get unique data name for this CatalogEntry.
214: *
215: * @return namespace:typeName
216: */
217: // public String getDataName() {
218: // return schema.getNamespace().toString() + ":"+ schema.getTypeName();
219: // }
220: //
221: // public Object resource() {
222: // try {
223: // return getFeatureSource();
224: // } catch (IOException e) {
225: // return null;
226: // }
227: // }
228: /**
229: * Metadata names from metadata.keySet().
230: *
231: * @return metadata names mentioned in metadata.keySet();
232: */
233: public String[] getMetadataNames() {
234: return (String[]) metadata.keySet().toArray(
235: new String[metadata.size()]);
236: }
237:
238: /**
239: * Map of metadata by name.
240: *
241: * @return Map of metadata by name
242: */
243: public Map metadata() {
244: return Collections.unmodifiableMap(metadata);
245: }
246:
247: /** Manages listener lists for FeatureSource implementation */
248: public FeatureListenerManager listenerManager = new FeatureListenerManager();
249:
250: //
251: // Start of TypeEntry framework
252: //
253: public String getTypeName() {
254: return schema.getTypeName();
255: }
256:
257: // public FeatureType getSchema() {
258: // return schema;
259: // }
260: /**
261: * Create a new FeatueSource allowing interaction with content.
262: * <p>
263: * Subclass may optionally implement:
264: * <ul>
265: * <li>FeatureStore - to allow read/write access
266: * <li>FeatureLocking - for locking support
267: * </ul>
268: * This choice may even be made a runtime (allowing the api
269: * to represent a readonly file).
270: * </p>
271: * <p>
272: * Several default implemenations are provided
273: *
274: * @return FeatureLocking allowing access to content.
275: */
276: // public FeatureSource getFeatureSource() throws IOException {
277: // return createFeatureSource();
278: // }
279: /**
280: * Access a FeatureReader providing access to Feature information.
281: * <p>
282: * This implementation passes off responsibility to the following overrideable methods:
283: * <ul>
284: * <li>getFeatureReader(String typeName) - subclass *required* to implement
285: * </ul>
286: * </p>
287: * <p>If you can handle some aspects of Query natively (say expressions or reprojection) override the following:
288: * <li>
289: * <li>getFeatureReader(typeName, query) - override to handle query natively
290: * <li>getUnsupportedFilter(typeName, filter) - everything you cannot handle natively
291: * <li>getFeatureReader(String typeName) - you must implement this, but you could point it back to getFeatureReader( typeName, Query.ALL );
292: * </ul>
293: * </p>
294: */
295: public FeatureReader reader(Query query, Transaction transaction)
296: throws IOException {
297:
298: if (transaction == null) {
299: throw new NullPointerException(
300: "Transaction null, did you mean Transaction.AUTO_COMMIT");
301: }
302:
303: FeatureCollection features = createFeatureSource().getFeatures(
304: query);
305: FeatureReader reader = new DelegateFeatureReader(features
306: .getSchema(), features.features());
307:
308: //wrap in diff reader if transaction specified
309: if (!transaction.equals(Transaction.AUTO_COMMIT)) {
310: reader = new DiffFeatureReader(reader, state(transaction)
311: .diff());
312: }
313:
314: return reader;
315:
316: // Filter filter = query.getFilter();
317: // String typeName = query.getTypeName();
318: // String propertyNames[] = query.getPropertyNames();
319: //
320: // if (filter == null) {
321: // throw new NullPointerException("getFeatureReader requires Filter: "
322: // + "did you mean Filter.INCLUDE?");
323: // }
324: // if( typeName == null ){
325: // throw new NullPointerException(
326: // "getFeatureReader requires typeName: "
327: // + "use getTypeNames() for a list of available types");
328: // }
329: // if (transaction == null) {
330: // throw new NullPointerException(
331: // "getFeatureReader requires Transaction: "
332: // + "did you mean to use Transaction.AUTO_COMMIT?");
333: // }
334: // FeatureType featureType = schema;
335: //
336: // if( propertyNames != null || query.getCoordinateSystem() != null ){
337: // try {
338: // featureType = DataUtilities.createSubType( featureType, propertyNames, query.getCoordinateSystem() );
339: // } catch (SchemaException e) {
340: // LOGGER.log( Level.FINEST, e.getMessage(), e);
341: // throw new DataSourceException( "Could not create Feature Type for query", e );
342: //
343: // }
344: // }
345: // if ( filter == Filter.EXCLUDE || filter.equals( Filter.EXCLUDE )) {
346: // return new EmptyFeatureReader(featureType);
347: // }
348: // //GR: allow subclases to implement as much filtering as they can,
349: // //by returning just it's unsupperted filter
350: // filter = getUnsupportedFilter( filter);
351: // if(filter == null){
352: // throw new NullPointerException("getUnsupportedFilter shouldn't return null. Do you mean Filter.INCLUDE?");
353: // }
354: //
355: // // This calls our subclass "simple" implementation
356: // // All other functionality will be built as a reader around
357: // // this class
358: // //
359: // FeatureReader reader = createReader( query);
360: //
361: // if (!filter.equals( Filter.INCLUDE ) ) {
362: // reader = new FilteringFeatureReader(reader, filter);
363: // }
364: //
365: // if (transaction != Transaction.AUTO_COMMIT) {
366: // Diff diff = state(transaction).diff();
367: // reader = new DiffFeatureReader(reader, diff);
368: // }
369: //
370: // if (!featureType.equals(reader.getFeatureType())) {
371: // LOGGER.fine("Recasting feature type to subtype by using a ReTypeFeatureReader");
372: // reader = new ReTypeFeatureReader(reader, featureType);
373: // }
374: //
375: // if (query.getMaxFeatures() != Query.DEFAULT_MAX) {
376: // reader = new MaxFeatureReader(reader, query.getMaxFeatures());
377: // }
378: // return reader;
379: }
380:
381: TypeDiffState state(Transaction transaction) {
382: synchronized (transaction) {
383: TypeDiffState state = (TypeDiffState) transaction
384: .getState(this );
385:
386: if (state == null) {
387: state = new TypeDiffState(this );
388: transaction.putState(this , state);
389: }
390:
391: return state;
392: }
393: }
394:
395: public void fireAdded(Feature newFeature, Transaction transaction) {
396: Envelope bounds = newFeature != null ? newFeature.getBounds()
397: : null;
398: listenerManager.fireFeaturesAdded(schema.getTypeName(),
399: transaction, bounds, false);
400: }
401:
402: public void fireRemoved(Feature removedFeature,
403: Transaction transaction) {
404: Envelope bounds = removedFeature != null ? removedFeature
405: .getBounds() : null;
406: listenerManager.fireFeaturesRemoved(schema.getTypeName(),
407: transaction, bounds, false);
408: }
409:
410: public void fireChanged(Feature before, Feature after,
411: Transaction transaction) {
412: String typeName = after.getFeatureType().getTypeName();
413: Envelope bounds = new Envelope();
414: bounds.expandToInclude(before.getBounds());
415: bounds.expandToInclude(after.getBounds());
416: listenerManager.fireFeaturesChanged(typeName, transaction,
417: bounds, false);
418: }
419:
420: //
421: // Start of Overrides
422: //
423: public abstract FeatureSource createFeatureSource();
424:
425: /**
426: * Override to provide readonly access
427: * @param schema
428: * @return FeatureSource backed by this TypeEntry.
429: */
430: // protected FeatureSource createFeatureSource() {
431: // return new AbstractFeatureSource() {
432: // public DataStore getDataStore() {
433: // return parent;
434: // }
435: // public void addFeatureListener( FeatureListener listener ) {
436: // listeners.addFeatureListener( this, listener );
437: // }
438: // public void removeFeatureListener( FeatureListener listener ) {
439: // listeners.addFeatureListener( this, listener );
440: // }
441: // public FeatureType getSchema() {
442: // return schema;
443: // }
444: // };
445: // }
446: /**
447: * Create the FeatureSource, override for your own custom implementation.
448: * <p>
449: * Default implementation makes use of DataStore getReader( ... ), and listenerManager.
450: * </p>
451: */
452: protected FeatureSource createFeatureSource(
453: final FeatureType featureType) {
454: return new AbstractFeatureSource() {
455: public DataStore getDataStore() {
456: return parent;
457: }
458:
459: public void addFeatureListener(FeatureListener listener) {
460: listenerManager.addFeatureListener(this , listener);
461: }
462:
463: public void removeFeatureListener(FeatureListener listener) {
464: listenerManager.removeFeatureListener(this , listener);
465: }
466:
467: public FeatureType getSchema() {
468: return featureType;
469: }
470: };
471: }
472:
473: /**
474: * Create the FeatureStore, override for your own custom implementation.
475: */
476: protected FeatureStore createFeatureStore() {
477:
478: // This implementation needs FeatureWriters to work
479: // please provide your own override for a datastore that does not
480: // support FeatureWriters (like WFS).
481: //
482: return new AbstractFeatureStore() {
483: public DataStore getDataStore() {
484: return parent;
485: }
486:
487: public void addFeatureListener(FeatureListener listener) {
488: listenerManager.addFeatureListener(this , listener);
489: }
490:
491: public void removeFeatureListener(FeatureListener listener) {
492: listenerManager.removeFeatureListener(this , listener);
493: }
494:
495: public FeatureType getSchema() {
496: return schema;
497: }
498: };
499: }
500:
501: /**
502: * Create the FeatureLocking, override for your own custom implementation.
503: * <p>
504: * Warning: The default implementation of this method uses lockingManger.
505: * You must override this method if you support your own locking system (like WFS).
506: * <p>
507: */
508: protected FeatureLocking createFeatureLocking() {
509: return new AbstractFeatureLocking() {
510: public DataStore getDataStore() {
511: return parent;
512: }
513:
514: public void addFeatureListener(FeatureListener listener) {
515: listenerManager.addFeatureListener(this , listener);
516: }
517:
518: public void removeFeatureListener(FeatureListener listener) {
519: listenerManager.removeFeatureListener(this , listener);
520: }
521:
522: public FeatureType getSchema() {
523: return schema;
524: }
525: };
526: }
527:
528: /**
529: * Create a reader for this query.
530: * <p>
531: * Subclass must override this to actually aquire content.
532: * </p>
533: * @param typeName
534: * @param query
535: * @return FeatureReader for all content
536: */
537: public FeatureReader createReader() {
538: return new EmptyFeatureReader(schema);
539: }
540:
541: /**
542: * GR: this method is called from inside getFeatureReader(Query ,Transaction )
543: * to allow subclasses return an optimized FeatureReader wich supports the
544: * filter and attributes truncation specified in <code>query</code>
545: * <p>
546: * A subclass that supports the creation of such an optimized FeatureReader
547: * shold override this method. Otherwise, it just returns
548: * <code>getFeatureReader(typeName)</code>
549: * <p>
550: */
551: protected FeatureReader createReader(Query query)
552: throws IOException {
553: return createReader();
554: }
555:
556: /**
557: * GR: if a subclass supports filtering, it should override this method
558: * to return the unsupported part of the passed filter, so a
559: * FilteringFeatureReader will be constructed upon it. Otherwise it will
560: * just return the same filter.
561: * <p>
562: * If the complete filter is supported, the subclass must return <code>Filter.INCLUDE</code>
563: * </p>
564: */
565: protected Filter getUnsupportedFilter(Filter filter) {
566: return filter;
567: }
568:
569: /* (non-Javadoc)
570: * @see org.geotools.data.DataStore#getFeatureWriter(java.lang.String, org.geotools.data.Transaction)
571: */
572: public FeatureWriter writer(Transaction transaction)
573: throws IOException {
574: if (transaction == null) {
575: throw new NullPointerException(
576: "getFeatureWriter requires Transaction: "
577: + "did you mean to use Transaction.AUTO_COMMIT?");
578: }
579:
580: FeatureWriter writer;
581:
582: if (transaction == Transaction.AUTO_COMMIT) {
583: writer = createWriter();
584: } else {
585: writer = state(transaction).writer();
586: }
587:
588: if (parent.getLockingManager() != null
589: && parent.getLockingManager() instanceof InProcessLockingManager) {
590: // subclass has not provided locking so we will
591: // fake it with InProcess locks
592: InProcessLockingManager lockingManger = (InProcessLockingManager) parent
593: .getLockingManager();
594: writer = lockingManger.checkedWriter(writer, transaction);
595: }
596: return writer;
597: }
598:
599: /**
600: * Low level feature writer access.
601: * <p>
602: * This is the only method you must implement to aquire content.
603: * </p>
604: * @return Subclass must supply a FeatureWriter
605: */
606: protected FeatureWriter createWriter() {
607: return new EmptyFeatureWriter(schema);
608: }
609:
610: /**
611: * It would be great to kill this method, and add a "skipToEnd" method to featureWriter?
612: * <p>
613: * Override this if you can provide a native optimization for this.
614: * (aka copy file, open the file in append mode, replace origional on close).
615: * </p>
616: */
617: protected FeatureWriter createAppend(Transaction transaction)
618: throws IOException {
619: FeatureWriter writer = writer(transaction);
620: while (writer.hasNext()) {
621: writer.next(); // Hmmm this would be a use for skip() then?
622: }
623: return writer;
624: }
625:
626: }
|