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.caching.impl;
017:
018: import com.vividsolutions.jts.geom.Envelope;
019:
020: import org.geotools.caching.DataCache;
021: import org.geotools.caching.FeatureIndex;
022: import org.geotools.caching.QueryTracker;
023:
024: import org.geotools.data.AbstractDataStore;
025: import org.geotools.data.DataSourceException;
026: import org.geotools.data.DataStore;
027: import org.geotools.data.DataUtilities;
028: import org.geotools.data.DefaultTransaction;
029: import org.geotools.data.FeatureReader;
030: import org.geotools.data.FeatureSource;
031: import org.geotools.data.FeatureWriter;
032: import org.geotools.data.InProcessLockingManager;
033: import org.geotools.data.LockingManager;
034: import org.geotools.data.Query;
035: import org.geotools.data.Transaction;
036:
037: import org.geotools.feature.Feature;
038: import org.geotools.feature.FeatureCollection;
039: import org.geotools.feature.FeatureIterator;
040: import org.geotools.feature.FeatureType;
041: import org.geotools.feature.IllegalAttributeException;
042: import org.geotools.feature.SchemaException;
043: import org.geotools.feature.SimpleFeature;
044:
045: import org.opengis.filter.Filter;
046:
047: import java.io.IOException;
048:
049: import java.util.ArrayList;
050: import java.util.HashMap;
051: import java.util.Iterator;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.NoSuchElementException;
055: import java.util.logging.Level;
056:
057: /** Implementation of DataCache that uses in-memory storage,
058: * spatial query tracker and spatial index.
059: *
060: * IMPORTANT : for the time being, this class provides cache facility only
061: * when using getView(Query) method. Other methods simply delegate to source DataStore.
062: *
063: * @task use this class to design AbstractDataCache,
064: * which would be the parent of all DataCache implementation.
065: *
066: * @author Christophe Rousson, SoC 2007, CRG-ULAVAL
067: *
068: */
069: public class InMemoryDataCache extends AbstractDataStore implements
070: DataCache {
071: /**
072: * The source DataStore, from where to get original features
073: */
074: private final DataStore source;
075:
076: /**
077: * Trackers to keep relationships between queries and features.
078: * engines is a list of CacheInternalEngine, one engine per feature type in DS.
079: * Each engine is composed of :
080: * - a query tracker, intance of QueryTracker
081: * - a feature storage, instance of FeatureIndex
082: */
083: private final HashMap engines;
084:
085: /** Creates a new DataCache on top of DataStore ds.
086: *
087: * @param ds the DataStore to cache.
088: */
089: public InMemoryDataCache(DataStore ds) {
090: this .source = ds;
091: this .engines = new HashMap();
092:
093: try {
094: String[] types = ds.getTypeNames();
095:
096: for (int i = 0; i < types.length; i++) {
097: CacheInternalEngine engine = new CacheInternalEngine(
098: this , ds.getSchema(types[i]));
099: engines.put(types[i], engine);
100: }
101: } catch (IOException e) {
102: // TODO Auto-generated catch block
103: throw (RuntimeException) new RuntimeException()
104: .initCause(e);
105: }
106: }
107:
108: public void clear() {
109: // TODO Auto-generated method stub
110: }
111:
112: public void flush() throws IllegalStateException {
113: // TODO Auto-generated method stub
114: }
115:
116: public long getHits() {
117: // TODO Auto-generated method stub
118: return 0;
119: }
120:
121: public void createSchema(FeatureType ft) throws IOException {
122: source.createSchema(ft);
123:
124: CacheInternalEngine engine = new CacheInternalEngine(this , ft);
125: engines.put(ft.getTypeName(), engine);
126: }
127:
128: /*public FeatureReader getFeatureReader(Query q, Transaction t)
129: throws IOException {
130: t.
131: return source.getFeatureReader(q, t);
132: }*/
133:
134: /*public FeatureSource getFeatureSource(String ft)
135: throws IOException {
136: // TODO Auto-generated method stub
137: return getEngine(ft).getIndex() ;
138: }*/
139: private CacheInternalEngine getEngine(String ft) {
140: return (CacheInternalEngine) engines.get(ft);
141: }
142:
143: /*public FeatureWriter getFeatureWriter(String arg0, Transaction arg1)
144: throws IOException {
145: return source.getFeatureWriter(arg0, arg1);
146: }*/
147:
148: /*public FeatureWriter getFeatureWriter(String arg0, Filter arg1, Transaction arg2)
149: throws IOException {
150: // TODO Auto-generated method stub
151: return source.getFeatureWriter(arg0, arg1, arg2);
152: }*/
153:
154: /*public FeatureWriter getFeatureWriterAppend(String arg0, Transaction arg1)
155: throws IOException {
156: return source.getFeatureWriterAppend(arg0, arg1);
157: }*/
158:
159: /*public LockingManager getLockingManager() {
160: return source.getLockingManager();
161: }*/
162: public FeatureType getSchema(String ft) throws IOException {
163: return getEngine(ft).getType();
164: }
165:
166: public String[] getTypeNames() throws IOException {
167: return (String[]) engines.keySet().toArray(
168: new String[engines.keySet().size()]);
169: }
170:
171: /* (non-Javadoc)
172: * @see org.geotools.data.DataStore#getView(org.geotools.data.Query)
173: *
174: * This is the important method :
175: *
176: * Sequence proposed to process user query :
177: *
178: * user query
179: * -> match query in tracker
180: * -> dowload missing data from source
181: * -> add new data to cache
182: * -> register query in tracker
183: * -> read cache
184: * -> answer to query
185: *
186: */
187: public FeatureSource getView(Query q) throws IOException,
188: SchemaException {
189: assert (q.getTypeName() != null);
190:
191: CacheInternalEngine engine = getEngine(q.getTypeName());
192:
193: if (engine == null) {
194: throw new SchemaException("Type not found : "
195: + q.getTypeName());
196: }
197:
198: QueryTracker tracker = engine.getTracker();
199: FeatureIndex index = getEngine(q.getTypeName()).getIndex();
200: Query m = tracker.match(q);
201: FeatureSource in = source.getView(m);
202: FeatureCollection fc = in.getFeatures();
203:
204: // FIXME what if the query oversize the cache ?
205: if (fc.size() > 0) {
206: FeatureIterator i = fc.features();
207:
208: while (i.hasNext()) {
209: index.add((Feature) i.next());
210: }
211:
212: fc.close(i);
213: }
214:
215: tracker.register(m);
216:
217: // if query q could not be turned into a "smaller" query, ie a query that yield a smaller set of features,
218: // returns directly collection obtained from source, rather than reading the cache.
219: if (m.equals(q)) {
220: return in;
221: } else {
222: return index.getView(q);
223: }
224: }
225:
226: public void updateSchema(String ftname, FeatureType ft) {
227: try {
228: source.updateSchema(ftname, ft);
229: engines.remove(ftname);
230:
231: CacheInternalEngine engine = new CacheInternalEngine(this ,
232: ft);
233: engines.put(ftname, engine);
234: } catch (IOException e) {
235: AbstractDataStore.LOGGER.log(Level.SEVERE,
236: "Exception when updating schema", e);
237: }
238: }
239:
240: /* (non-Javadoc)
241: * @see org.geotools.data.AbstractDataStore#getFeatureReader(java.lang.String)
242: * Copied from MemoryDataStore#getFeatureReader(java.lang.String)
243: */
244: protected FeatureReader getFeatureReader(final String typeName)
245: throws IOException {
246: return new FeatureReader() {
247: FeatureType featureType = source.getSchema(typeName);
248: Iterator iterator = source.getFeatureSource(typeName)
249: .getFeatures().iterator();
250:
251: public FeatureType getFeatureType() {
252: return featureType;
253: }
254:
255: public Feature next() throws IOException,
256: IllegalAttributeException, NoSuchElementException {
257: if (iterator == null) {
258: throw new IOException(
259: "Feature Reader has been closed");
260: }
261:
262: try {
263: return featureType.duplicate((Feature) iterator
264: .next());
265: } catch (NoSuchElementException end) {
266: throw new DataSourceException(
267: "There are no more Features", end);
268: }
269: }
270:
271: public boolean hasNext() {
272: return (iterator != null) && iterator.hasNext();
273: }
274:
275: public void close() {
276: if (iterator != null) {
277: iterator = null;
278: }
279:
280: if (featureType != null) {
281: featureType = null;
282: }
283: }
284: };
285: }
286:
287: protected Envelope getBounds(Query query) throws IOException {
288: try {
289: return source.getView(query).getBounds();
290: } catch (SchemaException e) {
291: throw (IOException) new IOException().initCause(e);
292: }
293: }
294:
295: protected int getCount(Query query) throws IOException {
296: try {
297: return source.getView(query).getCount(Query.ALL);
298: } catch (SchemaException e) {
299: throw (IOException) new IOException().initCause(e);
300: }
301: }
302:
303: protected FeatureWriter createFeatureWriter(final String typeName,
304: final Transaction transaction) throws IOException {
305: // Not sure of what I am doing
306: // If I pass provided transaction to source.getFeatureWriter, two transaction.commit() are needed to complete transaction.
307: FeatureWriter writer = source.getFeatureWriter(typeName,
308: Transaction.AUTO_COMMIT);
309:
310: return writer;
311:
312: /*return new FeatureWriter() {
313: FeatureType featureType = getSchema(typeName);
314: FeatureCollection contents = source.getFeatureSource(typeName).getFeatures() ;
315: Iterator iterator = contents.iterator();
316: SimpleFeature live = null;
317: Feature current = null; // current Feature returned to user
318: public FeatureType getFeatureType() {
319: return featureType;
320: }
321: public Feature next() throws IOException, NoSuchElementException {
322: if (hasNext()) {
323: // existing content
324: live = (SimpleFeature) iterator.next();
325: try {
326: current = featureType.duplicate(live);
327: } catch (IllegalAttributeException e) {
328: throw new DataSourceException("Unable to edit " + live.getID() + " of "
329: + typeName);
330: }
331: } else {
332: // new content
333: live = null;
334: try {
335: current = DataUtilities.template(featureType);
336: } catch (IllegalAttributeException e) {
337: throw new DataSourceException("Unable to add additional Features of "
338: + typeName);
339: }
340: }
341: return current;
342: }
343: public void remove() throws IOException {
344: if (contents == null) {
345: throw new IOException("FeatureWriter has been closed");
346: }
347: if (current == null) {
348: throw new IOException("No feature available to remove");
349: }
350: if (live != null) {
351: // remove existing content
352: iterator.remove();
353: listenerManager.fireFeaturesRemoved(typeName, transaction,
354: live.getBounds(), true);
355: live = null;
356: current = null;
357: } else {
358: // cancel add new content
359: current = null;
360: }
361: }
362: public void write() throws IOException {
363: if (contents == null) {
364: throw new IOException("FeatureWriter has been closed");
365: }
366: if (current == null) {
367: throw new IOException("No feature available to write");
368: }
369: if (live != null) {
370: if (live.equals(current)) {
371: // no modifications made to current
372: //
373: live = null;
374: current = null;
375: } else {
376: // accept modifications
377: //
378: try {
379: live.setAttributes(current.getAttributes(null));
380: } catch (IllegalAttributeException e) {
381: throw new DataSourceException("Unable to accept modifications to "
382: + live.getID() + " on " + typeName);
383: }
384: Envelope bounds = new Envelope();
385: bounds.expandToInclude(live.getBounds());
386: bounds.expandToInclude(current.getBounds());
387: listenerManager.fireFeaturesChanged(typeName, transaction,
388: bounds, true);
389: live = null;
390: current = null;
391: }
392: } else {
393: // add new content
394: //
395: //contents.put(current.getID(), current);
396: contents.add(current) ;
397: listenerManager.fireFeaturesAdded(typeName, transaction,
398: current.getBounds(), true);
399: current = null;
400: }
401: }
402: public boolean hasNext() throws IOException {
403: if (contents == null) {
404: throw new IOException("FeatureWriter has been closed");
405: }
406: return (iterator != null) && iterator.hasNext();
407: }
408: public void close(){
409: if (iterator != null) {
410: iterator = null;
411: }
412: if (featureType != null) {
413: featureType = null;
414: }
415: contents = null;
416: current = null;
417: live = null;
418: }
419: };*/
420: }
421:
422: /*protected InProcessLockingManager createLockingManager() {
423: return null ;
424: }*/
425:
426: /*protected FeatureWriter getFeatureWriter(String typeName) throws IOException {
427:
428: }*/
429: }
|