/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.data.postgis;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.logging.Logger;
import org.geotools.data.DefaultQuery;
import org.geotools.data.FeatureReader;
import org.geotools.data.Transaction;
import org.geotools.data.postgis.fidmapper.VersionedFIDMapper;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
/**
* Provides forward only access to the feature differences
*
* @author aaime
* @since 2.4
*
*
* @source $URL:
* http://svn.osgeo.org/geotools/trunk/modules/unsupported/postgis-versioned/src/main/java
* /org/geotools/data/postgis/FeatureDiffReader.java $
*/
public class FeatureDiffReaderImpl implements org.geotools.data.FeatureDiffReader {
/** The logger for the postgis module. */
protected static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.data.postgis");
private FeatureReader<SimpleFeatureType, SimpleFeature> fromReader;
private FeatureReader<SimpleFeatureType, SimpleFeature> toReader;
private RevisionInfo fromVersion;
private RevisionInfo toVersion;
private VersionedFIDMapper mapper;
private Transaction transaction;
private VersionedPostgisDataStore store;
private FeatureReader<SimpleFeatureType, SimpleFeature> deletedReader;
private FeatureReader<SimpleFeatureType, SimpleFeature> createdReader;
private SimpleFeatureType externalFeatureType;
private FeatureDiffImpl lastDiff;
private ModifiedFeatureIds modifiedIds;
public FeatureDiffReaderImpl(VersionedPostgisDataStore store, Transaction transaction,
SimpleFeatureType externalFeatureType, RevisionInfo fromVersion,
RevisionInfo toVersion, VersionedFIDMapper mapper, ModifiedFeatureIds modifiedIds)
throws IOException {
this.store = store;
this.transaction = transaction;
this.fromVersion = fromVersion;
this.toVersion = toVersion;
this.externalFeatureType = externalFeatureType;
this.mapper = mapper;
this.modifiedIds = modifiedIds;
initReaders();
}
/**
* Allows to clone a diff reader, this makes it possible to scroll over the same diffs with
* multiple readers at the same time (reset allows only for multiple isolated scans)
*
* @param other
* @throws IOException
*/
public FeatureDiffReaderImpl(FeatureDiffReaderImpl other) throws IOException {
this.store = other.store;
this.transaction = other.transaction;
this.fromVersion = other.fromVersion;
this.toVersion = other.toVersion;
this.externalFeatureType = other.externalFeatureType;
this.mapper = other.mapper;
this.modifiedIds = other.modifiedIds;
initReaders();
}
void initReaders() throws IOException {
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
// TODO: extract only pk attributes for the delete reader, no need for the others
if (fromVersion.revision > toVersion.revision) {
createdReader = readerFromIdsRevision(ff, null, modifiedIds.deleted,
modifiedIds.fromRevision);
deletedReader = readerFromIdsRevision(ff, null, modifiedIds.created,
modifiedIds.toRevision);
fromReader = readerFromIdsRevision(ff, mapper, modifiedIds.modified,
modifiedIds.toRevision);
toReader = readerFromIdsRevision(ff, mapper, modifiedIds.modified,
modifiedIds.fromRevision);
} else {
createdReader = readerFromIdsRevision(ff, null, modifiedIds.created,
modifiedIds.toRevision);
deletedReader = readerFromIdsRevision(ff, null, modifiedIds.deleted,
modifiedIds.fromRevision);
fromReader = readerFromIdsRevision(ff, mapper, modifiedIds.modified,
modifiedIds.fromRevision);
toReader = readerFromIdsRevision(ff, mapper, modifiedIds.modified,
modifiedIds.toRevision);
}
}
/**
* Returns a feature reader for the specified fids and revision, or null if the fid set is empty
*
* @param ff
* @param fids
* @param ri
* @return
* @throws IOException
*/
FeatureReader<SimpleFeatureType, SimpleFeature> readerFromIdsRevision(FilterFactory ff,
VersionedFIDMapper mapper, Set fids, RevisionInfo ri) throws IOException {
if (fids != null && !fids.isEmpty()) {
Filter fidFilter = store.buildFidFilter(fids);
Filter versionFilter = store.buildVersionedFilter(externalFeatureType.getTypeName(),
fidFilter, ri);
DefaultQuery query = new DefaultQuery(externalFeatureType.getTypeName(), versionFilter);
if (mapper != null) {
List sort = new ArrayList(mapper.getColumnCount() - 1);
for (int i = 0; i < mapper.getColumnCount(); i++) {
String colName = mapper.getColumnName(i);
if (!"revision".equals(colName))
sort.add(ff.sort(colName, SortOrder.DESCENDING));
}
query.setSortBy((SortBy[]) sort.toArray(new SortBy[sort.size()]));
}
return store.wrapped.getFeatureReader(query, transaction);
} else {
return null;
}
}
/**
* The first version used to compute the difference
*
* @return
*/
public String getFromVersion() {
return fromVersion.getVersion();
}
/**
* The second version used to computed the difference
*
* @return
*/
public String getToVersion() {
return toVersion.getVersion();
}
/**
* Returns the feature type whose features are diffed with this reader
*
* @return
*/
public SimpleFeatureType getSchema() {
return externalFeatureType;
}
/**
* Reads the next FeatureDifference
*
* @return The next FeatureDifference
*
* @throws IOException
* If an error occurs reading the FeatureDifference.
* @throws NoSuchElementException
* If there are no more Features in the Reader.
*/
public FeatureDiffImpl next() throws IOException, NoSuchElementException {
// check we have something, and force reader mantainance as well, so that
// we make sure finished ones are nullified
if (!hasNext())
throw new NoSuchElementException("No more diffs in this reader");
if (createdReader != null) {
return new FeatureDiffImpl(null, gatherNextUnversionedFeature(createdReader));
} else if (deletedReader != null) {
return new FeatureDiffImpl(gatherNextUnversionedFeature(deletedReader), null);
} else {
FeatureDiffImpl diff = lastDiff;
lastDiff = null;
return diff;
}
}
/**
* Turns a versioned feature into the extenal equivalent, with modified fid and without the
* versioning columns
*
* @param f
* @return
*/
private SimpleFeature gatherNextUnversionedFeature(
final FeatureReader<SimpleFeatureType, SimpleFeature> fr) throws IOException {
final SimpleFeature f = fr.next();
final Object[] attributes = new Object[externalFeatureType.getAttributeCount()];
for (int i = 0; i < externalFeatureType.getAttributeCount(); i++) {
attributes[i] = f.getAttribute(externalFeatureType.getDescriptor(i).getLocalName());
}
String id = mapper.getUnversionedFid(f.getID());
return SimpleFeatureBuilder.build(externalFeatureType, attributes, id);
}
/**
* Query whether this FeatureDiffReader has another FeatureDiff.
*
* @return True if there are more differences to be read. In other words, true if calls to next
* would return a feature rather than throwing an exception.
*
* @throws IOException
* If an error occurs determining if there are more Features.
*/
public boolean hasNext() throws IOException {
// we first scan created, then removed, then the two that need to be diffed (which are
// guaranteed to be parallel, so check just one)
if (createdReader != null) {
if (createdReader.hasNext()) {
return true;
} else {
createdReader.close();
createdReader = null;
}
}
if (deletedReader != null) {
if (deletedReader.hasNext()) {
return true;
} else {
deletedReader.close();
deletedReader = null;
}
}
// this is harder... we may have features that have changed between fromVersion and
// toVersion, but which are equal in those two (typical case, rollback). So we really
// need to compute the diff and move forward if there's no difference at all
if (lastDiff != null)
return true;
if (fromReader != null && toReader != null) {
while (true) {
if (!fromReader.hasNext()) {
lastDiff = null;
fromReader.close();
toReader.close();
fromReader = null;
toReader = null;
return false;
}
// compute field by field difference
SimpleFeature from = gatherNextUnversionedFeature(fromReader);
SimpleFeature to = gatherNextUnversionedFeature(toReader);
FeatureDiffImpl diff = new FeatureDiffImpl(from, to);
if (diff.getChangedAttributes().size() != 0) {
lastDiff = diff;
return true;
}
}
} else {
return false; // closed;
}
}
/**
* Resets the reader to the initial position
*
* @throws IOException
*/
public void reset() throws IOException {
close();
initReaders();
}
/**
* Release the underlying resources associated with this stream.
*
* @throws IOException
* DOCUMENT ME!
*/
public void close() throws IOException {
if (createdReader != null) {
createdReader.close();
createdReader = null;
}
if (deletedReader != null) {
deletedReader.close();
deletedReader = null;
}
if (fromReader != null) {
fromReader.close();
fromReader = null;
}
if (toReader != null) {
toReader.close();
toReader = null;
}
}
protected void finalize() throws Throwable {
if (createdReader != null || deletedReader != null || fromReader != null || toReader != null) {
LOGGER.warning("There's code leaaving the feature diff readers open!");
close();
}
}
}