/* * 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.session; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.sis.feature.FeatureExt; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.FeatureReader; import org.geotoolkit.data.FeatureWriter; import org.geotoolkit.data.memory.MemoryFeatureStore; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.factory.FactoryFinder; import org.geotoolkit.util.NamesExt; import org.geotoolkit.geometry.DefaultBoundingBox; import org.apache.sis.referencing.CommonCRS; import org.junit.Before; import org.junit.Test; import org.opengis.util.GenericName; import org.opengis.filter.identity.FeatureId; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.opengis.filter.FilterFactory2; import org.opengis.referencing.NoSuchAuthorityCodeException; import org.opengis.util.FactoryException; import static org.junit.Assert.*; import org.opengis.feature.Feature; import org.opengis.feature.FeatureType; import org.apache.sis.internal.feature.AttributeConvention; /** * * @author Johann Sorel (Geomatys) */ public class SessionTest extends org.geotoolkit.test.TestBase { private static final double TOLERANCE = 1e-7; private static final GeometryFactory GF = new GeometryFactory(); private static final FilterFactory2 FF = (FilterFactory2) FactoryFinder.getFilterFactory(null); private MemoryFeatureStore store = new MemoryFeatureStore(); public SessionTest() { } @Before public void setUp() throws Exception { store = new MemoryFeatureStore(); final FeatureTypeBuilder builder = new FeatureTypeBuilder(); //create the schema final GenericName name = NamesExt.create("http://test.com", "TestSchema1"); builder.setName(name); builder.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY); builder.addAttribute(Point.class).setName("geom").setCRS(CommonCRS.WGS84.normalizedGeographic()); builder.addAttribute(String.class).setName("string"); builder.addAttribute(Double.class).setName("double"); builder.addAttribute(Date.class).setName("date"); final FeatureType type = builder.build(); store.createFeatureType(type); //create a few features FeatureWriter writer = store.getFeatureWriter(QueryBuilder.all(name.toString())); try{ Feature f = writer.next(); f.setPropertyValue("geom", GF.createPoint(new Coordinate(3, 30))); f.setPropertyValue("string", "hop3"); f.setPropertyValue("double", 3d); f.setPropertyValue("date", new Date(1000L)); writer.write(); f = writer.next(); f.setPropertyValue("geom", GF.createPoint(new Coordinate(1, 10))); f.setPropertyValue("string", "hop1"); f.setPropertyValue("double", 1d); f.setPropertyValue("date", new Date(100000L)); writer.write(); f = writer.next(); f.setPropertyValue("geom", GF.createPoint(new Coordinate(2, 20))); f.setPropertyValue("string", "hop2"); f.setPropertyValue("double", 2d); f.setPropertyValue("date", new Date(10000L)); writer.write(); }finally{ writer.close(); } //quick count check FeatureReader reader = store.getFeatureReader(QueryBuilder.all(name.toString())); int count = 0; try{ while(reader.hasNext()){ reader.next(); count++; } }finally{ reader.close(); } assertEquals(count, 3); assertEquals(store.getCount(QueryBuilder.all(name.toString())), 3); } @Test public void testSessionReader() throws Exception { final GenericName name = store.getNames().iterator().next(); final QueryBuilder qb = new QueryBuilder(); Query query; //create an asynchrone session final Session session = store.createSession(true); //test simple reader with no modification ------------------------------ qb.setTypeName(name); query = qb.buildQuery(); assertEquals(store.getCount(query),3); assertEquals(session.getCount(query),3); assertFalse(session.hasPendingChanges()); //test an iterator------------------------------------------------------ FeatureIterator reader = session.getFeatureIterator(QueryBuilder.sorted(name.toString(), new SortBy[]{FF.sort("string", SortOrder.ASCENDING)})); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop1"); reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop2"); reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop3"); assertFalse(reader.hasNext()); }finally{ reader.close(); } //---------------------------------------------------------------------- //test adding new features---------------------------------------------- //---------------------------------------------------------------------- final Feature sfb = store.getFeatureType(name.toString()).newInstance(); sfb.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(), "temporary"); sfb.setPropertyValue("string", "hop4"); sfb.setPropertyValue("double", 2.5d); sfb.setPropertyValue("date", new Date(100L)); session.addFeatures(name.toString(), Collections.singletonList(sfb)); //check that he new feature is available in the session but not in the datastore qb.reset(); qb.setTypeName(name); query = qb.buildQuery(); assertEquals(store.getCount(query),3); assertEquals(session.getCount(query),4); assertTrue(session.hasPendingChanges()); reader = session.getFeatureIterator(QueryBuilder.filtered(name.toString(), FF.equals(FF.literal("hop4"), FF.property("string")))); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop4"); assertEquals(sf.getPropertyValue("double"),2.5d); assertEquals(sf.getPropertyValue("date"),new Date(100L)); assertFalse(reader.hasNext()); }finally{ reader.close(); } //check that the sorting order has been preserve within the session reader = session.getFeatureIterator(QueryBuilder.sorted(name.toString(),new SortBy[]{FF.sort("double", SortOrder.ASCENDING)})); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("double"),1d); reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("double"),2d); reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("double"),2.5d); reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("double"),3d); assertFalse(reader.hasNext()); }finally{ reader.close(); } //check that the new feature is available in the session and in the datastore session.commit(); assertEquals(store.getCount(query),4); assertEquals(session.getCount(query),4); assertFalse(session.hasPendingChanges()); //make a more deep test to find our feature reader = session.getFeatureIterator(QueryBuilder.filtered(name.toString(), FF.equals(FF.literal("hop4"), FF.property("string")))); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop4"); assertEquals(sf.getPropertyValue("double"),2.5d); assertEquals(sf.getPropertyValue("date"),new Date(100L)); assertFalse(reader.hasNext()); }finally{ reader.close(); } reader = store.getFeatureReader(QueryBuilder.filtered(name.toString(), FF.equals(FF.literal("hop4"), FF.property("string")))); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop4"); assertEquals(sf.getPropertyValue("double"),2.5d); assertEquals(sf.getPropertyValue("date"),new Date(100L)); assertFalse(reader.hasNext()); }finally{ reader.close(); } } @Test public void testSessionRemoveDelta() throws DataStoreException{ final GenericName name = store.getNames().iterator().next(); final QueryBuilder qb = new QueryBuilder(); Query query; //create an asynchrone session final Session session = store.createSession(true); //---------------------------------------------------------------------- //test removing feature------------------------------------------------- //---------------------------------------------------------------------- //check that the feature exist FeatureIterator reader = session.getFeatureIterator(QueryBuilder.filtered(name.toString(), FF.equals(FF.literal("hop3"), FF.property("string")))); try{ Feature sf; reader.hasNext(); sf = reader.next(); assertEquals(sf.getPropertyValue("string"),"hop3"); assertEquals(sf.getPropertyValue("double"),3d); assertEquals(sf.getPropertyValue("date"),new Date(1000L)); assertFalse(reader.hasNext()); }finally{ reader.close(); } assertFalse(session.hasPendingChanges()); //remove the feature session.removeFeatures(name.toString(), FF.equals(FF.literal("hop3"), FF.property("string"))); //check that the feature is removed in the session but not in the datastore qb.reset(); qb.setTypeName(name); query = qb.buildQuery(); assertEquals(store.getCount(query),3); assertEquals(session.getCount(query),2); assertTrue(session.hasPendingChanges()); qb.reset(); qb.setTypeName(name); qb.setFilter(FF.equals(FF.literal("hop3"), FF.property("string"))); query = qb.buildQuery(); assertEquals(1,store.getCount(query)); assertEquals(0,session.getCount(query)); //check that the new feature is removed in the session and in the datastore session.commit(); assertEquals(0,store.getCount(query)); assertEquals(0,session.getCount(query)); assertFalse(session.hasPendingChanges()); //sanity check, count everything qb.reset(); qb.setTypeName(name); query = qb.buildQuery(); assertEquals(2,store.getCount(query)); assertEquals(2,session.getCount(query)); assertFalse(session.hasPendingChanges()); } @Test public void testSessionModifyDelta() throws DataStoreException, NoSuchAuthorityCodeException, FactoryException{ final GenericName name = store.getNames().iterator().next(); final QueryBuilder qb = new QueryBuilder(); Query query; //create an asynchrone session final Session session = store.createSession(true); //---------------------------------------------------------------------- //test modifying feature------------------------------------------------ //---------------------------------------------------------------------- Point newPt = GF.createPoint(new Coordinate(5, 50)); Map<String,Object> values = new HashMap<>(); values.put("double", 15d); values.put("geom", newPt); session.updateFeatures(name.toString(), FF.equals(FF.property("double"), FF.literal(2d)), values); //check we have a modification ----------------------------------------- qb.reset(); qb.setTypeName(name); query = qb.buildQuery(); assertEquals(store.getCount(query),3); assertEquals(session.getCount(query),3); assertTrue(session.hasPendingChanges()); //check the geometry is changed FeatureIterator ite = session.getFeatureIterator(query); boolean found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(15d)){ found = true; assertTrue(newPt.getCoordinate().equals2D( ((Point)f.getPropertyValue("geom")).getCoordinate() )); } } ite.close(); if(!found){ fail("modified feature not found."); } //check the query modified feature is correctly reprojected ------------ qb.reset(); qb.setCRS(CommonCRS.WGS84.geographic()); qb.setTypeName(name); query = qb.buildQuery(); assertEquals(store.getCount(query),3); assertEquals(session.getCount(query),3); assertTrue(session.hasPendingChanges()); //check the geometry is changed ite = session.getFeatureIterator(query); found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(15d)){ found = true; Point pt = (Point)f.getPropertyValue("geom"); assertEquals(50d, pt.getCoordinate().x, TOLERANCE); assertEquals(5d, pt.getCoordinate().y, TOLERANCE); } } ite.close(); if(!found){ fail("modified feature not found."); } //make a second change on the same feature ----------------------------- newPt = GF.createPoint(new Coordinate(9, 90)); values = new HashMap<>(); values.put("geom", newPt); session.updateFeatures(name.toString(), FF.equals(FF.property("double"), FF.literal(15d)), values); qb.reset(); qb.setCRS(CommonCRS.WGS84.geographic()); qb.setTypeName(name); query = qb.buildQuery(); //check the geometry is changed ite = session.getFeatureIterator(query); found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(15d)){ found = true; Point pt = (Point)f.getPropertyValue("geom"); assertEquals(90d, pt.getCoordinate().x, TOLERANCE); assertEquals(9d, pt.getCoordinate().y, TOLERANCE); } } ite.close(); if(!found){ fail("modified feature not found."); } session.rollback(); assertFalse(session.hasPendingChanges()); //check that two modification doesnt conflict eachother qb.reset(); qb.setTypeName(name); query = qb.buildQuery(); String desc = "double"; ite = session.getFeatureIterator(query); FeatureId id1 = FeatureExt.getId(ite.next()); FeatureId id2 = FeatureExt.getId(ite.next()); ite.close(); session.updateFeatures(name.toString(), FF.id(Collections.singleton(id1)), Collections.singletonMap(desc, 50d)); session.updateFeatures(name.toString(), FF.id(Collections.singleton(id2)), Collections.singletonMap(desc, 100d)); qb.reset(); qb.setTypeName(name); qb.setFilter(FF.id(Collections.singleton(id1))); query = qb.buildQuery(); ite = session.getFeatureIterator(query); int count = 0; while(ite.hasNext()){ Feature f = ite.next(); assertEquals(id1,FeatureExt.getId(f)); assertEquals(50d, f.getPropertyValue("double")); count++; } ite.close(); assertEquals(1, count); qb.reset(); qb.setTypeName(name); qb.setFilter(FF.id(Collections.singleton(id2))); query = qb.buildQuery(); ite = session.getFeatureIterator(query); count = 0; while(ite.hasNext()){ Feature f = ite.next(); assertEquals(id2,FeatureExt.getId(f)); assertEquals(100d, f.getPropertyValue("double")); count++; } ite.close(); assertEquals(1, count); //todo must handle count and envelope before testing more //check the modification is visible only in the session // qb.reset(); // qb.setTypeName(name); // qb.setFilter(FF.equals(FF.property("double"), FF.literal(15d))); // query = qb.buildQuery(); // // assertEquals(store.getCount(query),0); // assertEquals(session.getCount(query),1); // assertTrue(session.hasPendingChanges()); //check the modification is visible only in the session // qb.reset(); // qb.setTypeName(name); // qb.setFilter(FF.equals(FF.property("double"), FF.literal(2d))); // query = qb.buildQuery(); // // assertEquals(store.getCount(query),1); // assertEquals(session.getCount(query),0); // assertTrue(session.hasPendingChanges()); } @Test public void testSessionModifyDeltaFilter() throws DataStoreException, NoSuchAuthorityCodeException, FactoryException{ final GenericName name = store.getNames().iterator().next(); final QueryBuilder qb = new QueryBuilder(); Query query; //create an asynchrone session final Session session = store.createSession(true); final Point newPt = GF.createPoint(new Coordinate(50, 1)); final Map<String,Object> values = new HashMap<>(); values.put("geom", newPt); session.updateFeatures(name.toString(), FF.equals(FF.property("double"), FF.literal(2d)), values); //check we have a modification in data crs ----------------------------- DefaultBoundingBox bbox = new DefaultBoundingBox(CommonCRS.WGS84.normalizedGeographic()); bbox.setRange(0, 49, 51); bbox.setRange(1, 0, 2); qb.reset(); qb.setTypeName(name); qb.setFilter(FF.bbox(FF.property("geom"), bbox)); query = qb.buildQuery(); assertEquals(0,store.getCount(query)); assertEquals(1,session.getCount(query)); assertTrue(session.hasPendingChanges()); //check the geometry is changed FeatureIterator ite = session.getFeatureIterator(query); boolean found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(2d)){ found = true; final Point pt = (Point)f.getPropertyValue("geom"); //we expect axes the same way assertEquals(newPt.getCoordinate().x, pt.getCoordinate().x,TOLERANCE); assertEquals(newPt.getCoordinate().y, pt.getCoordinate().y,TOLERANCE); } } ite.close(); if(!found){ fail("modified feature not found."); } //check we have a modification in another crs -------------------------- bbox = new DefaultBoundingBox(CommonCRS.WGS84.geographic()); bbox.setRange(1, 49, 51); bbox.setRange(0, 0, 2); qb.reset(); qb.setTypeName(name); qb.setFilter(FF.bbox(FF.property("geom"), bbox)); query = qb.buildQuery(); //check the geometry is changed ite = session.getFeatureIterator(query); found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(2d)){ found = true; final Point pt = (Point)f.getPropertyValue("geom"); //we expect axes the same way assertEquals(newPt.getCoordinate().x, pt.getCoordinate().x, TOLERANCE); assertEquals(newPt.getCoordinate().y, pt.getCoordinate().y, TOLERANCE); } } ite.close(); if(!found){ fail("modified feature not found."); } session.commit(); //check valid once in the datastore------------------------------------- ite = session.getFeatureIterator(query); found = false; while(ite.hasNext()){ Feature f = ite.next(); if(f.getProperty("double").getValue().equals(2d)){ found = true; final Point pt = (Point)f.getPropertyValue("geom"); //we expect axes the same way assertEquals(newPt.getCoordinate().x, pt.getCoordinate().x, TOLERANCE); assertEquals(newPt.getCoordinate().y, pt.getCoordinate().y, TOLERANCE); } } ite.close(); if(!found){ fail("modified feature not found."); } } }