/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2009-2015, Geomatys
*
* 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.geotoolkit.data;
import com.vividsolutions.jts.geom.Geometry;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.sis.feature.FeatureExt;
import org.apache.sis.feature.FeatureTypeExt;
import org.apache.sis.storage.DataStoreException;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.data.session.Session;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.util.NamesExt;
import org.apache.sis.referencing.CRS;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.opengis.util.GenericName;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.identity.FeatureId;
import org.opengis.filter.sort.SortBy;
import org.opengis.filter.sort.SortOrder;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
import org.opengis.feature.PropertyType;
import static org.junit.Assert.*;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.util.Utilities;
import org.opengis.metadata.Identifier;
import org.opengis.feature.Operation;
import org.apache.sis.internal.feature.AttributeConvention;
/**
* Generic reading tests for datastore.
* Tests schemas names and readers with queries.
*
* @author Johann Sorel (Geomatys)
*/
public abstract class AbstractReadingTests{
protected static final double DELTA = 0.000000001d;
private static final FilterFactory FF = FactoryFinder.getFilterFactory(null);
public static class ExpectedResult{
public ExpectedResult(final GenericName name, final FeatureType type, final int size, final Envelope env){
this.name = name;
this.type = type;
this.size = size;
this.env = env;
}
public GenericName name;
public FeatureType type;
public int size;
public Envelope env;
}
@Before
public void setUp() {
}
@After
public void tearDown() {
}
protected abstract FeatureStore getDataStore();
protected abstract Set<GenericName> getExpectedNames();
protected abstract List<ExpectedResult> getReaderTests();
@Test
public void testDataStore(){
final FeatureStore store = getDataStore();
assertNotNull(store);
}
/**
* test session creation.
*/
@Test
public void testSession(){
final FeatureStore store = getDataStore();
Session async_session = store.createSession(true);
Session sync_session = store.createSession(false);
assertNotNull(async_session);
assertTrue(async_session.isAsynchrone());
assertNotNull(sync_session);
assertFalse(sync_session.isAsynchrone());
}
/**
* test schema names
*/
@Test
public void testSchemas() throws Exception{
final FeatureStore store = getDataStore();
final Set<GenericName> expectedTypes = getExpectedNames();
//need at least one type to test
assertTrue(expectedTypes.size() > 0);
//check names-----------------------------------------------------------
final Set<GenericName> founds = store.getNames();
assertNotNull(founds);
assertTrue(expectedTypes.size() == founds.size());
assertTrue(expectedTypes.containsAll(founds));
assertTrue(founds.containsAll(expectedTypes));
for(GenericName name : founds){
assertNotNull(name);
assertNotNull(store.getFeatureType(name.toString()));
}
//check type names------------------------------------------------------
final Set<GenericName> typeNames = store.getNames();
assertNotNull(typeNames);
assertTrue(typeNames.size() == founds.size());
check:
for (GenericName typeName : typeNames) {
for(GenericName n : founds){
if(n.tip().toString().equals(typeName.tip().toString())){
assertNotNull(typeName);
FeatureType type1 = store.getFeatureType(typeName.toString());
FeatureType type2 = store.getFeatureType(n.toString());
assertNotNull(type1);
assertNotNull(type2);
assertTrue(type1.equals(type2));
continue check;
}
}
throw new Exception("A typename has no matching Name, featurestore is not consistent.");
}
//check error on wrong type names---------------------------------------
try{
store.getFeatureType(NamesExt.create("http://not", "exist").toString());
throw new Exception("Asking for a schema that doesnt exist should have raised a featurestore exception.");
}catch(DataStoreException ex){
//ok
}
try{
store.getFeatureType("not-exist_stuff");
throw new Exception("Asking for a schema that doesnt exist should have raised a featurestore exception.");
}catch(DataStoreException ex){
//ok
}
}
/**
* test feature reader.
*/
@Test
public void testReader() throws Exception{
final FeatureStore store = getDataStore();
final List<ExpectedResult> candidates = getReaderTests();
//need at least one type to test
assertTrue(candidates.size() > 0);
for(final ExpectedResult candidate : candidates){
final GenericName name = candidate.name;
final FeatureType type = store.getFeatureType(name.toString());
assertNotNull(type);
assertTrue(FeatureTypeExt.equalsIgnoreConvention(candidate.type, type));
testCounts(store, candidate);
testReaders(store, candidate);
testBounds(store, candidate);
}
}
/**
* test different count with filters.
*/
private void testCounts(final FeatureStore store, final ExpectedResult candidate) throws Exception{
assertEquals(candidate.size, store.getCount(QueryBuilder.all(candidate.name)));
//todo make more generic count tests
}
/**
* test different bounds with filters.
*/
private void testBounds(final FeatureStore store, final ExpectedResult candidate) throws Exception{
Envelope res = store.getEnvelope(QueryBuilder.all(candidate.name));
if(candidate.env == null){
//looks like we are testing a geometryless feature
assertNull(res);
return;
}
assertNotNull(res);
assertEquals(res.getMinimum(0), candidate.env.getMinimum(0), DELTA);
assertEquals(res.getMinimum(1), candidate.env.getMinimum(1), DELTA);
assertEquals(res.getMaximum(0), candidate.env.getMaximum(0), DELTA);
assertEquals(res.getMaximum(1), candidate.env.getMaximum(1), DELTA);
//todo make generic bounds tests
}
/**
* test different readers.
*/
private void testReaders(final FeatureStore store, final ExpectedResult candidate) throws Exception{
final FeatureType type = store.getFeatureType(candidate.name.toString());
final Collection<? extends PropertyType> properties = type.getProperties(true);
final QueryBuilder qb = new QueryBuilder();
Query query = null;
try{
store.getFeatureReader(query);
throw new Exception("Asking for a reader without any query whould raise an error.");
}catch(Exception ex){
//ok
}
query = QueryBuilder.all(NamesExt.create(NamesExt.getNamespace(candidate.name), candidate.name.tip().toString()+"fgresfds_not_exist"));
try{
store.getFeatureReader(query);
throw new Exception("Asking for a reader with a wrong name should raise a featurestore exception.");
}catch(DataStoreException ex){
//ok
}
query = QueryBuilder.all(NamesExt.create(NamesExt.getNamespace(candidate.name)+"resfsdfsdf_not_exist", candidate.name.tip().toString()));
try{
store.getFeatureReader(query);
throw new Exception("Asking for a reader with a wrong namespace should raise a featurestore exception.");
}catch(DataStoreException ex){
//ok
}
//crs ------------------------------------------------------------------
final CoordinateReferenceSystem originalCRS = FeatureExt.getCRS(type);
if(originalCRS!= null){
CoordinateReferenceSystem testCRS = CRS.forCode("EPSG:3395");
if(Utilities.equalsIgnoreMetadata(originalCRS, testCRS)){
//change the test crs
testCRS = CommonCRS.WGS84.geographic();
}
//handle geometry as object since they can be ISO or JTS
final Map<FeatureId,Object> inOriginal = new HashMap<FeatureId, Object>();
FeatureReader ite = store.getFeatureReader(QueryBuilder.all(candidate.name));
try{
while(ite.hasNext()){
final Feature f = ite.next();
final FeatureId id = FeatureExt.getId(f);
final Object geom = f.getPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString());
assertNotNull(id);
assertNotNull(geom);
inOriginal.put(id,geom);
}
}finally{
ite.close();
}
//check that geometries are different in another projection
//it's not a exact check but it's better than nothing
qb.reset();
qb.setTypeName(candidate.name);
qb.setCRS(testCRS);
ite = store.getFeatureReader(qb.buildQuery());
try{
while(ite.hasNext()){
final Feature f = ite.next();
final FeatureId id = FeatureExt.getId(f);
final Object original = inOriginal.get(id);
final Object reprojected = f.getPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString());
assertNotNull(id);
assertNotNull(original);
assertNotNull(reprojected);
assertNotSame(original, reprojected);
}
}finally{
ite.close();
}
}
//property -------------------------------------------------------------
//check only id query
query = QueryBuilder.fids(candidate.name.toString());
FeatureReader ite = store.getFeatureReader(query);
FeatureType limited = ite.getFeatureType();
assertNotNull(limited);
assertTrue(limited.getProperties(true).size() == 1);
try{
while(ite.hasNext()){
final Feature f = ite.next();
assertNotNull(FeatureExt.getId(f));
}
}finally{
ite.close();
}
for(final PropertyType desc : properties){
if(desc instanceof Operation) continue;
qb.reset();
qb.setTypeName(candidate.name);
qb.setProperties(new String[]{desc.getName().tip().toString()});
query = qb.buildQuery();
ite = store.getFeatureReader(query);
limited = ite.getFeatureType();
assertNotNull(limited);
assertTrue(limited.getProperties(true).size() == 1);
assertNotNull(limited.getProperty(desc.getName().toString()));
try{
while(ite.hasNext()){
final Feature f = ite.next();
assertNotNull(f.getProperty(desc.getName().toString()));
}
}finally{
ite.close();
}
}
//sort by --------------------------------------------------------------
for(final PropertyType desc : properties){
if(!(desc instanceof AttributeType)){
continue;
}
final AttributeType att = (AttributeType) desc;
if(att.getMaximumOccurs()>1){
//do not test sort by on multi occurence properties
continue;
}
final Class clazz = att.getValueClass();
if(!Comparable.class.isAssignableFrom(clazz) || Geometry.class.isAssignableFrom(clazz)){
//can not make a sort by on this attribut.
continue;
}
qb.reset();
qb.setTypeName(candidate.name);
qb.setSortBy(new SortBy[]{FF.sort(desc.getName().tip().toString(), SortOrder.ASCENDING)});
query = qb.buildQuery();
//count should not change with a sort by
assertEquals(candidate.size, store.getCount(query));
FeatureReader reader = store.getFeatureReader(query);
try{
Comparable last = null;
while(reader.hasNext()){
final Feature f = reader.next();
Object obj = f.getProperty(desc.getName().toString()).getValue();
if (obj instanceof Identifier) obj = ((Identifier) obj).getCode();
final Comparable current = (Comparable) obj;
if(current != null){
if(last != null){
//check we have the correct order.
assertTrue( current.compareTo(last) >= 0 );
}
last = current;
}else{
//any restriction about where should be placed the feature with null values ? before ? after ?
}
}
}finally{
reader.close();
}
qb.reset();
qb.setTypeName(candidate.name);
qb.setSortBy(new SortBy[]{FF.sort(desc.getName().tip().toString(), SortOrder.DESCENDING)});
query = qb.buildQuery();
//count should not change with a sort by
assertEquals(candidate.size, store.getCount(query));
reader = store.getFeatureReader(query);
try{
Comparable last = null;
while(reader.hasNext()){
final Feature f = reader.next();
Object obj = f.getProperty(desc.getName().toString()).getValue();
if (obj instanceof Identifier) obj = ((Identifier) obj).getCode();
final Comparable current = (Comparable) obj;
if(current != null){
if(last != null){
//check we have the correct order.
assertTrue( current.compareTo(last) <= 0 );
}
last = current;
}else{
//any restriction about where should be placed the feature with null values ? before ? after ?
}
}
}finally{
reader.close();
}
}
//start ----------------------------------------------------------------
if(candidate.size > 1){
qb.reset();
qb.setTypeName(candidate.name);
query = qb.buildQuery();
List<FeatureId> ids = new ArrayList<FeatureId>();
ite = store.getFeatureReader(query);
try{
while(ite.hasNext()){
ids.add(FeatureExt.getId(ite.next()));
}
}finally{
ite.close();
}
qb.reset();
qb.setTypeName(candidate.name);
//skip the first element
qb.setStartIndex(1);
query = qb.buildQuery();
ite = store.getFeatureReader(query);
try{
int i = 1;
while(ite.hasNext()){
assertEquals(FeatureExt.getId(ite.next()), ids.get(i));
i++;
}
}finally{
ite.close();
}
}
//max ------------------------------------------------------------------
if(candidate.size > 1){
qb.reset();
qb.setTypeName(candidate.name);
//skip the first element
qb.setMaxFeatures(1);
query = qb.buildQuery();
int i = 0;
ite = store.getFeatureReader(query);
try{
while(ite.hasNext()){
ite.next();
i++;
}
}finally{
ite.close();
}
assertEquals(1, i);
}
//filter ---------------------------------------------------------------
//filters are tested more deeply in the filter module
//we just make a few tests here for sanity check
//todo should we make more deep tests ?
Set<FeatureId> ids = new HashSet<>();
ite = store.getFeatureReader(QueryBuilder.fids(candidate.name.toString()));
try{
//peek only one on two ids
boolean oneOnTwo = true;
while(ite.hasNext()){
final Feature feature = ite.next();
if(oneOnTwo){
ids.add(FeatureExt.getId(feature));
}
oneOnTwo = !oneOnTwo;
}
}finally{
ite.close();
}
Set<FeatureId> remaining = new HashSet<>(ids);
ite = store.getFeatureReader(QueryBuilder.filtered(candidate.name.toString(),FF.id(ids)));
try{
while(ite.hasNext()){
remaining.remove(FeatureExt.getId(ite.next()));
}
}finally{
ite.close();
}
assertTrue(remaining.isEmpty() );
}
}