/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2011, 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.geoserver.data.versioning.decorator;
import static org.geoserver.data.versioning.decorator.VersionFilters.getUnversioningFilter;
import static org.geoserver.data.versioning.decorator.VersionFilters.getVersioningFilter;
import java.awt.RenderingHints.Key;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
import org.geogit.api.RevTree;
import org.geogit.repository.Repository;
import org.geogit.repository.WorkingTree;
import org.geoserver.data.versioning.VersioningFeatureSource;
import org.geotools.data.DataAccess;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureSource;
import org.geotools.data.Query;
import org.geotools.data.QueryCapabilities;
import org.geotools.data.ResourceInfo;
import org.geotools.feature.FeatureCollection;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.Id;
import org.opengis.filter.identity.Identifier;
import org.opengis.filter.identity.ResourceId;
/**
* Provides support for {@link ResourceId} filtering by means of wrapping an
* unversioned feature source and accessing the versioning information in the
* versioning subsystem provided by the argument {@link DataAccessDecorator}.
*
* @author groldan
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public class FeatureSourceDecorator<T extends FeatureType, F extends Feature>
implements VersioningFeatureSource<T, F> {
private static final Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geoserver.data.geogit.decorator");
protected final FeatureSource<T, F> unversioned;
protected final Repository repository;
public FeatureSourceDecorator(final FeatureSource unversioned,
final Repository repository) {
this.unversioned = unversioned;
this.repository = repository;
}
/**
* @return {@code true} if this is a versioned Feature Type, {@code false}
* otherwise.
*/
public boolean isVersioned() {
final Name name = getSchema().getName();
return isVersioned(name, repository);
}
public static boolean isVersioned(final Name typeName,
final Repository repository) {
final WorkingTree workingTree = repository.getWorkingTree();
final boolean isVersioned = workingTree.hasRoot(typeName);
return isVersioned;
}
/**
* @return the object id of the current HEAD's commit
*/
public RevTree getCurrentVersion() {
final Name name = getName();
RevTree headVersion = repository.getWorkingTree().getHeadVersion(name);
return headVersion;
}
/**
* @precondition {@code typeName != null && versioningFilter != null}
* @precondition {@code versioningFilter.getIdentifiers().size() > 0}
* @postcondition {@code $return != null}
* @param versioningFilter
* @param extraQuery
* @return
* @throws IOException
*/
protected FeatureCollection getFeatures(final Id versioningFilter,
final Query extraQuery) throws IOException {
// Assert.notNull(versioningFilter);
// Assert.isTrue(versioningFilter.getIdentifiers().size() > 0);
final FeatureType featureType = getSchema();
Iterable<Feature> versionQuery;
if (versioningFilter == null
|| versioningFilter.getIdentifiers().size() == 0) {
versionQuery = new QueryFeatureCollector(repository, featureType,
extraQuery);
} else {
final Set<Identifier> identifiers = versioningFilter
.getIdentifiers();
final Set<ResourceId> resourceIds = new HashSet<ResourceId>();
for (Identifier id : identifiers) {
if (id instanceof ResourceId) {
resourceIds.add((ResourceId) id);
}
}
if (resourceIds.size() == 0) {
throw new IllegalArgumentException("At least one "
+ ResourceId.class.getName() + " should be provided: "
+ identifiers);
}
if (extraQuery.getVersion() != null) {
versionQuery = new ResourceIdQueryFeatureCollector(repository,
featureType, resourceIds, extraQuery);
} else {
versionQuery = new ResourceIdFeatureCollector(repository,
featureType, resourceIds);
}
}
DefaultVersionedFeatureCollection features = new DefaultVersionedFeatureCollection(
null, (SimpleFeatureType) featureType);
for (Feature f : versionQuery) {
boolean contained = features.contains(f);
LOGGER.info("Feature " + (contained ? "is" : "is not")
+ " found in the collection: " + f);
features.add((SimpleFeature) f);
}
return features;
}
/**
* @see org.geotools.data.FeatureSource#getDataStore()
*/
@Override
public DataAccess<T, F> getDataStore() {
return unversioned.getDataStore();
}
/**
* @see org.geotools.data.FeatureSource#getBounds(org.geotools.data.Query)
*/
@Override
public ReferencedEnvelope getBounds(Query query) throws IOException {
if (!isVersioned() || null == getVersioningFilter(query.getFilter())) {
return unversioned.getBounds(query);
}
return getFeatures(query).getBounds();
}
/**
* @see org.geotools.data.FeatureSource#getCount(org.geotools.data.Query)
*/
@Override
public int getCount(Query query) throws IOException {
if (!isVersioned() || null == getVersioningFilter(query.getFilter())) {
return unversioned.getCount(query);
}
return getFeatures(query).size();
}
/**
* @see org.geotools.data.FeatureSource#getFeatures()
* @see #getFeatures(Query)
*/
@Override
public FeatureCollection<T, F> getFeatures() throws IOException {
return getFeatures(Query.ALL);
}
/**
* @see org.geotools.data.FeatureSource#getFeatures(org.opengis.filter.Filter)
* @see #getFeatures(Query)
*/
@Override
public FeatureCollection<T, F> getFeatures(Filter filter)
throws IOException {
return getFeatures(namedQuery(filter));
}
private Query namedQuery(Filter filter) {
Name name = getName();
String typeName = name.getLocalPart();
URI namespace;
try {
namespace = new URI(name.getNamespaceURI());
} catch (URISyntaxException e) {
namespace = null;
}
int maxFeartures = Integer.MAX_VALUE;
String[] propNames = null;
String handle = null;
Query query = new Query(typeName, namespace, filter, maxFeartures,
propNames, handle);
return query;
}
/**
* Performs the given query with knowledge of feature versioning.
* <p>
* In case the feature type this source refers to is not versioned, defers
* to the underlying {@link FeatureSource}.
* </p>
* If the Feature Type is versioned, and the Query filter contains an
* {@link Id} filter with {@link ResourceId} predicates, defers to the
* versioning backend (GeoGIT) to spply the requested versions of the
* feature identified by the {@link ResourceId}s; othwewise just wraps the
* wrapped FeatureSource results into a decorating FeatureCollection that
* assigns {@link ResourceId} instead of {@link FeatureId} to returned
* Features, containing the current version hash, as in
* {@code <original feature id>@<current version id>}. </p>
*
* @see org.geotools.data.FeatureSource#getFeatures(org.geotools.data.Query)
*/
@Override
public FeatureCollection<T, F> getFeatures(Query query) throws IOException {
Id versioningFilter;
if (!isVersioned()) {
return unversioned.getFeatures(query);
}
versioningFilter = getVersioningFilter(query.getFilter());
if (versioningFilter == null && query.getVersion() == null) {
FeatureCollection<T, F> delegate = unversioned.getFeatures(query);
final RevTree currentTypeTree = getCurrentVersion();
return createFeatureCollection(delegate, currentTypeTree);
}
Filter unversionedFilter = getUnversioningFilter(query.getFilter());
FeatureCollection coll = getFeatures(versioningFilter, query);
return coll.subCollection(unversionedFilter);
}
protected FeatureCollection<T, F> createFeatureCollection(
FeatureCollection<T, F> delegate, RevTree currentTypeTree) {
return new ResourceIdAssigningFeatureCollection(delegate, this,
currentTypeTree);
}
// / directly deferred methods
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getName()
*/
@Override
public Name getName() {
return unversioned.getName();
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getInfo()
*/
@Override
public ResourceInfo getInfo() {
return unversioned.getInfo();
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getQueryCapabilities()
*/
@Override
public QueryCapabilities getQueryCapabilities() {
return unversioned.getQueryCapabilities();
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#addFeatureListener(org.geotools.data.FeatureListener)
*/
@Override
public void addFeatureListener(FeatureListener listener) {
unversioned.addFeatureListener(listener);
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#removeFeatureListener(org.geotools.data.FeatureListener)
*/
@Override
public void removeFeatureListener(FeatureListener listener) {
unversioned.removeFeatureListener(listener);
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getSchema()
*/
@Override
public T getSchema() {
return unversioned.getSchema();
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getBounds()
*/
@Override
public ReferencedEnvelope getBounds() throws IOException {
return unversioned.getBounds();
}
/**
* Defers to the same method on the wrapped unversioned FeatureSource
*
* @see org.geotools.data.FeatureSource#getSupportedHints()
*/
@Override
public Set<Key> getSupportedHints() {
return unversioned.getSupportedHints();
}
}