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.postgis;
017:
018: import java.io.IOException;
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.List;
022: import java.util.Map;
023: import java.util.NoSuchElementException;
024: import java.util.Set;
025: import java.util.TreeSet;
026: import java.util.logging.Logger;
027:
028: import org.geotools.data.DataSourceException;
029: import org.geotools.data.DataUtilities;
030: import org.geotools.data.DefaultQuery;
031: import org.geotools.data.FeatureReader;
032: import org.geotools.data.Transaction;
033: import org.geotools.data.postgis.fidmapper.VersionedFIDMapper;
034: import org.geotools.factory.CommonFactoryFinder;
035: import org.geotools.feature.Feature;
036: import org.geotools.feature.FeatureType;
037: import org.geotools.feature.IllegalAttributeException;
038: import org.opengis.filter.Filter;
039: import org.opengis.filter.FilterFactory;
040: import org.opengis.filter.sort.SortBy;
041: import org.opengis.filter.sort.SortOrder;
042:
043: /**
044: * Provides forward only access to the feature differences
045: *
046: * @author aaime
047: * @since 2.4
048: *
049: */
050: public class FeatureDiffReader {
051: /** The logger for the postgis module. */
052: protected static final Logger LOGGER = org.geotools.util.logging.Logging
053: .getLogger("org.geotools.data.postgis");
054:
055: private FeatureReader fvReader;
056:
057: private FeatureReader tvReader;
058:
059: private RevisionInfo fromVersion;
060:
061: private RevisionInfo toVersion;
062:
063: private VersionedFIDMapper mapper;
064:
065: private Transaction transaction;
066:
067: private VersionedPostgisDataStore store;
068:
069: private FeatureReader deletedReader;
070:
071: private FeatureReader createdReader;
072:
073: private FeatureType externalFeatureType;
074:
075: private FeatureDiff lastDiff;
076:
077: private ModifiedFeatureIds modifiedIds;
078:
079: public FeatureDiffReader(VersionedPostgisDataStore store,
080: Transaction transaction, FeatureType externalFeatureType,
081: RevisionInfo fromVersion, RevisionInfo toVersion,
082: VersionedFIDMapper mapper, ModifiedFeatureIds modifiedIds)
083: throws IOException {
084: this .store = store;
085: this .transaction = transaction;
086: this .fromVersion = fromVersion;
087: this .toVersion = toVersion;
088: this .externalFeatureType = externalFeatureType;
089: this .mapper = mapper;
090: this .modifiedIds = modifiedIds;
091: initReaders();
092: }
093:
094: /**
095: * Allows to clone a diff reader, this makes it possible to scroll over the same diffs with
096: * multiple readers at the same time (reset allows only for multiple isolated scans)
097: *
098: * @param other
099: * @throws IOException
100: */
101: public FeatureDiffReader(FeatureDiffReader other)
102: throws IOException {
103: this .store = other.store;
104: this .transaction = other.transaction;
105: this .fromVersion = other.fromVersion;
106: this .toVersion = other.toVersion;
107: this .externalFeatureType = other.externalFeatureType;
108: this .mapper = other.mapper;
109: this .modifiedIds = other.modifiedIds;
110: initReaders();
111: }
112:
113: void initReaders() throws IOException {
114: FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
115:
116: // TODO: extract only pk attributes for the delete reader, no need for the others
117: if (fromVersion.revision > toVersion.revision) {
118: createdReader = readerFromIdsRevision(ff, null,
119: modifiedIds.deleted, toVersion);
120: deletedReader = readerFromIdsRevision(ff, null,
121: modifiedIds.created, fromVersion);
122: } else {
123: createdReader = readerFromIdsRevision(ff, null,
124: modifiedIds.created, toVersion);
125: deletedReader = readerFromIdsRevision(ff, null,
126: modifiedIds.deleted, fromVersion);
127: }
128: fvReader = readerFromIdsRevision(ff, mapper,
129: modifiedIds.modified, fromVersion);
130: tvReader = readerFromIdsRevision(ff, mapper,
131: modifiedIds.modified, toVersion);
132: }
133:
134: /**
135: * Returns a feature reader for the specified fids and revision, or null if the fid set is empty
136: *
137: * @param ff
138: * @param fids
139: * @param ri
140: * @return
141: * @throws IOException
142: */
143: FeatureReader readerFromIdsRevision(FilterFactory ff,
144: VersionedFIDMapper mapper, Set fids, RevisionInfo ri)
145: throws IOException {
146: if (fids != null && !fids.isEmpty()) {
147: Filter fidFilter = store.buildFidFilter(ff, fids);
148: Filter versionFilter = store.buildVersionedFilter(
149: externalFeatureType.getTypeName(), fidFilter, ri);
150: DefaultQuery query = new DefaultQuery(externalFeatureType
151: .getTypeName(), versionFilter);
152: if (mapper != null) {
153: List sort = new ArrayList(mapper.getColumnCount() - 1);
154: for (int i = 0; i < mapper.getColumnCount(); i++) {
155: String colName = mapper.getColumnName(i);
156: if (!"revision".equals(colName))
157: sort
158: .add(ff.sort(colName,
159: SortOrder.DESCENDING));
160: }
161: query.setSortBy((SortBy[]) sort.toArray(new SortBy[sort
162: .size()]));
163: }
164: return store.wrapped.getFeatureReader(query, transaction);
165: } else {
166: return null;
167: }
168: }
169:
170: /**
171: * The first version used to compute the difference
172: *
173: * @return
174: */
175: public String getFromVersion() {
176: return fromVersion.getVersion();
177: }
178:
179: /**
180: * The second version used to computed the difference
181: *
182: * @return
183: */
184: public String getToVersion() {
185: return toVersion.getVersion();
186: }
187:
188: /**
189: * Returns the feature type whose features are diffed with this reader
190: *
191: * @return
192: */
193: public FeatureType getSchema() {
194: return externalFeatureType;
195: }
196:
197: /**
198: * Reads the next FeatureDifference
199: *
200: * @return The next FeatureDifference
201: *
202: * @throws IOException
203: * If an error occurs reading the FeatureDifference.
204: * @throws NoSuchElementException
205: * If there are no more Features in the Reader.
206: */
207: public FeatureDiff next() throws IOException,
208: NoSuchElementException {
209: // check we have something, and force reader mantainance as well, so that
210: // we make sure finished ones are nullified
211: if (!hasNext())
212: throw new NoSuchElementException(
213: "No more diffs in this reader");
214: if (createdReader != null) {
215: return new FeatureDiff(null,
216: gatherNextUnversionedFeature(createdReader));
217: } else if (deletedReader != null) {
218: return new FeatureDiff(
219: gatherNextUnversionedFeature(deletedReader), null);
220: } else {
221: FeatureDiff diff = lastDiff;
222: lastDiff = null;
223: return diff;
224: }
225:
226: }
227:
228: /**
229: * Turns a versioned feature into the extenal equivalent, with modified fid and without the
230: * versioning columns
231: *
232: * @param f
233: * @return
234: * @throws IllegalAttributeException
235: */
236: private Feature gatherNextUnversionedFeature(final FeatureReader fr)
237: throws IOException {
238: try {
239: final Feature f = fr.next();
240: final Object[] attributes = new Object[externalFeatureType
241: .getAttributeCount()];
242: for (int i = 0; i < externalFeatureType.getAttributeCount(); i++) {
243: attributes[i] = f.getAttribute(externalFeatureType
244: .getAttributeType(i).getName());
245: }
246: String id = mapper.getUnversionedFid(f.getID());
247: return externalFeatureType.create(attributes, id);
248: } catch (IllegalAttributeException e) {
249: throw new DataSourceException(
250: "Could not properly load the fetures to diff: " + e);
251: }
252:
253: }
254:
255: /**
256: * Query whether this FeatureDiffReader has another FeatureDiff.
257: *
258: * @return True if there are more differences to be read. In other words, true if calls to next
259: * would return a feature rather than throwing an exception.
260: *
261: * @throws IOException
262: * If an error occurs determining if there are more Features.
263: */
264: public boolean hasNext() throws IOException {
265: // we first scan created, then removed, then the two that need to be diffed (which are
266: // guaranteed to be parallel, so check just one)
267: if (createdReader != null) {
268: if (createdReader.hasNext()) {
269: return true;
270: } else {
271: createdReader.close();
272: createdReader = null;
273: }
274: }
275: if (deletedReader != null) {
276: if (deletedReader.hasNext()) {
277: return true;
278: } else {
279: deletedReader.close();
280: deletedReader = null;
281: }
282: }
283: // this is harder... we may have features that have changed between fromVersion and
284: // toVersion, but which are equal in those two (typical case, rollback). So we really
285: // need to compute the diff and move forward if there's no difference at all
286: if (lastDiff != null)
287: return true;
288: if (fvReader != null && tvReader != null) {
289: while (true) {
290: if (!fvReader.hasNext()) {
291: lastDiff = null;
292: fvReader.close();
293: tvReader.close();
294: return false;
295: }
296: // compute field by field difference
297: Feature from = gatherNextUnversionedFeature(fvReader);
298: Feature to = gatherNextUnversionedFeature(tvReader);
299: FeatureDiff diff = new FeatureDiff(from, to);
300: if (diff.getChangedAttributes().size() != 0) {
301: lastDiff = diff;
302: return true;
303: }
304: }
305: } else {
306: return false; // closed;
307: }
308: }
309:
310: /**
311: * Resets the reader to the initial position
312: *
313: * @throws IOException
314: */
315: public void reset() throws IOException {
316: close();
317: initReaders();
318: }
319:
320: /**
321: * Release the underlying resources associated with this stream.
322: *
323: * @throws IOException
324: * DOCUMENT ME!
325: */
326: public void close() throws IOException {
327: if (createdReader != null) {
328: createdReader.close();
329: createdReader = null;
330: }
331: if (deletedReader != null) {
332: deletedReader.close();
333: deletedReader = null;
334: }
335: if (fvReader != null) {
336: fvReader.close();
337: fvReader = null;
338: }
339: if (tvReader != null) {
340: tvReader.close();
341: tvReader = null;
342: }
343: }
344:
345: protected void finalize() throws Throwable {
346: LOGGER
347: .warning("There's code leaaving the feature diff readers open!");
348: close();
349: }
350:
351: }
|