package org.geoserver.gss; import static java.util.Collections.*; import static org.geoserver.gss.GSSCore.*; import java.io.IOException; import org.geotools.data.DefaultQuery; import org.geotools.data.FeatureSource; import org.geotools.data.Query; import org.geotools.data.VersioningDataStore; import org.geotools.data.VersioningFeatureSource; import org.geotools.data.VersioningFeatureStore; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureIterator; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.filter.Id; import org.opengis.filter.PropertyIsEqualTo; import org.opengis.filter.sort.SortBy; import org.opengis.filter.sort.SortOrder; import org.w3c.dom.Document; import com.mockrunner.mock.web.MockHttpServletResponse; public class PostDiffTest extends GSSTestSupport { @Override protected void setUpInternal() throws Exception { super.setUpInternal(); } public void testConfictSchema() throws IOException { SimpleFeatureType ft = synchStore.getSchema("synch_conflicts"); // we expect the pk to be exposed, as it's a multi column one assertEquals(7, ft.getAttributeCount()); } public void testUnknownLayer() throws Exception { MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffUnknown.xml")); validate(response); Document dom = dom(response); print(dom); checkOws10Exception(dom, "InvalidParameterValue", "typeName"); } public void testInvalidFromVersion() throws Exception { MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInvalidFrom.xml")); validate(response); Document dom = dom(response); checkOws10Exception(dom, "InvalidParameterValue", "fromVersion"); } public void testEmptyRepeated() throws Exception { VersioningDataStore synch = (VersioningDataStore) getCatalog().getDataStoreByName("synch").getDataStore(null); VersioningFeatureSource restricted = (VersioningFeatureSource) synch.getFeatureSource("restricted"); long revisionStart = getLastRevision(restricted); MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffEmpty.xml")); checkPostDiffSuccessResponse(response); // check the lasts known Central revision is updated assertEquals(new Long(12), getLastCentralRevision(synch, "restricted")); // check we did not eat away a local revision number long revisionAfterFirst = getLastRevision(restricted); assertEquals(revisionStart, revisionAfterFirst); // execute a second empty update response = postAsServletResponse(root(true), loadTextResource("PostDiffEmptySecond.xml")); checkPostDiffSuccessResponse(response); // check the lasts known Central revision is updated assertEquals(new Long(24), getLastCentralRevision(synch, "restricted")); // check we did not eat away a local revision number long revisionAfterSecond = getLastRevision(restricted); assertEquals(revisionStart, revisionAfterSecond); } Long getLastCentralRevision(VersioningDataStore synch, String tableName) throws IOException { FeatureIterator<SimpleFeature> fi = null; try { FeatureSource<SimpleFeatureType, SimpleFeature> fs = synch.getFeatureSource(SYNCH_HISTORY); PropertyIsEqualTo tableFilter = ff.equals(ff.property("table_name"), ff.literal(tableName)); DefaultQuery q = new DefaultQuery(SYNCH_HISTORY, tableFilter); q.setSortBy(new SortBy[] {ff.sort("local_revision", SortOrder.DESCENDING), ff.sort("central_revision", SortOrder.DESCENDING)}); q.setMaxFeatures(1); fi = fs.getFeatures(q).features(); SimpleFeature f = (SimpleFeature) fi.next(); return (Long) f.getAttribute("central_revision"); } finally { if(fi != null) { fi.close(); } } } long getLastRevision(VersioningFeatureSource fs) throws IOException { FeatureIterator<SimpleFeature> fi = null; try { fi = fs.getLog("LAST", "FIRST", null, null, 1).features(); if(fi.hasNext()) { return (Long) fi.next().getAttribute("revision"); } else { return -1; } } finally { fi.close(); } } public void testEmptyTransaction() throws Exception { // a slightly different test for a failure encontered in real world testing // with a transaction element specified, but empty MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffEmptyTransaction.xml")); checkPostDiffSuccessResponse(response); // check the lasts known Central revision is updated VersioningDataStore synch = (VersioningDataStore) getCatalog().getDataStoreByName("synch") .getDataStore(null); FeatureSource<SimpleFeatureType, SimpleFeature> fs = synch.getFeatureSource(SYNCH_HISTORY); FeatureCollection fc = fs.getFeatures(ff.equals(ff.property("table_name"), ff .literal("restricted"))); FeatureIterator fi = fc.features(); SimpleFeature f = (SimpleFeature) fi.next(); fi.close(); assertEquals(12l, f.getAttribute("central_revision")); } /** * No local changes, no conflicts * @throws Exception */ public void testFirstSynch() throws Exception { // grab the datastore so that we can assess the initial situation FeatureSource<SimpleFeatureType, SimpleFeature> restricted = synchStore .getFeatureSource("restricted"); assertEquals(4, restricted.getCount(Query.ALL)); // get the response and do the basic checks MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInitial.xml")); checkPostDiffSuccessResponse(response); checkPostDiffInitialChanges(restricted); // check there are no conflicts assertEquals(0, gss.getActiveConflicts("restricted").size()); } /** * Local but not conflicting changes * @throws Exception */ public void testLocalChangesNoConflict() throws Exception { // grab the datastore so that we can make some changes that will not generate conflicts VersioningFeatureStore restricted = (VersioningFeatureStore) synchStore.getFeatureSource("restricted"); SimpleFeatureType schema = restricted.getSchema(); // modify the fourth feature, change its cat from 400 to 450 Id updateFilter = ff.id(singleton(ff.featureId("restricted.1b99be2b-2480-4742-ad52-95c294efda3b"))); restricted.modifyFeatures(schema.getDescriptor("cat"), 450, updateFilter); // remove the third feature Id removeFilter = ff.id(singleton(ff.featureId("restricted.c15e76ab-e44b-423e-8f85-f6d9927b878a"))); restricted.removeFeatures(removeFilter); assertEquals(3, restricted.getCount(Query.ALL)); // get the response and do the basic checks MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInitial.xml")); checkPostDiffSuccessResponse(response); checkPostDiffInitialChanges(restricted); // check there are no conflicts assertEquals(0, gss.getActiveConflicts("restricted").size()); // check the local changes are still there assertEquals(0, restricted.getCount(new DefaultQuery(null, removeFilter))); FeatureIterator<SimpleFeature> fi; fi = restricted.getFeatures(updateFilter).features(); assertTrue(fi.hasNext()); SimpleFeature f = fi.next(); fi.close(); assertEquals(450l, f.getAttribute("cat")); } /** * Sheer luck, the local changes are on the same feature, and are the same changes Central is pushing onto us * @throws Exception */ public void testCleanMerge() throws Exception { // grab the datastore VersioningFeatureStore restricted = (VersioningFeatureStore) synchStore.getFeatureSource("restricted"); SimpleFeatureType schema = restricted.getSchema(); // make the same changes as in the post diff Id updateFilter = ff.id(singleton(ff.featureId("restricted.be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4"))); restricted.modifyFeatures(schema.getDescriptor("cat"), -48, updateFilter); // remove the third feature Id removeFilter = ff.id(singleton(ff.featureId("restricted.d91fe390-bdc7-4b22-9316-2cd6c8737ef5"))); restricted.removeFeatures(removeFilter); assertEquals(3, restricted.getCount(Query.ALL)); // get the response and do the basic checks MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInitial.xml")); checkPostDiffSuccessResponse(response); checkPostDiffInitialChanges(restricted); // check there are no conflicts assertEquals(0, gss.getActiveConflicts("restricted").size()); } public void testDeleteConflict() throws Exception { // grab the datastore so that we can make some changes that will generate conflicts VersioningFeatureStore restricted = (VersioningFeatureStore) synchStore.getFeatureSource("restricted"); restricted.removeFeatures(ff.id(singleton(ff.featureId("restricted.be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4")))); assertEquals(3, restricted.getCount(Query.ALL)); // get the response and do the basic checks MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInitial.xml")); checkPostDiffSuccessResponse(response); checkPostDiffInitialChanges(restricted); // check we actually have stored the deletion conflict (the deleted feature has been updated // by central FeatureCollection<SimpleFeatureType, SimpleFeature> activeConflicts = gss.getActiveConflicts("restricted"); assertEquals(1, activeConflicts.size()); FeatureIterator<SimpleFeature> fi = activeConflicts.features(); SimpleFeature f = fi.next(); fi.close(); assertEquals("restricted", f.getAttribute("table_name")); assertEquals("be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4", f.getAttribute("feature_id")); assertEquals("c", f.getAttribute("state")); assertNull(f.getAttribute("local_feature")); } public void testUpdateConflict() throws Exception { // grab the datastore so that we can make some changes that will generate conflicts VersioningFeatureStore restricted = (VersioningFeatureStore) synchStore.getFeatureSource("restricted"); SimpleFeatureType schema = restricted.getSchema(); Id fidFilter = ff.id(singleton(ff.featureId("restricted.be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4"))); restricted.modifyFeatures(schema.getDescriptor("cat"), 123456, fidFilter); assertEquals(4, restricted.getCount(Query.ALL)); // get the response and do the basic checks MockHttpServletResponse response = postAsServletResponse(root(true), loadTextResource("PostDiffInitial.xml")); checkPostDiffSuccessResponse(response); checkPostDiffInitialChanges(restricted); // check we actually have stored the update conflict FeatureCollection<SimpleFeatureType, SimpleFeature> activeConflicts = gss.getActiveConflicts("restricted"); assertEquals(1, activeConflicts.size()); FeatureIterator<SimpleFeature> fi = activeConflicts.features(); SimpleFeature f = fi.next(); fi.close(); assertEquals("restricted", f.getAttribute("table_name")); assertEquals("be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4", f.getAttribute("feature_id")); assertEquals("c", f.getAttribute("state")); assertNotNull(f.getAttribute("local_feature")); // TODO: make sure the stored feature is the value before the rollback SimpleFeature preConflict = gss.fromGML3((String) f.getAttribute("local_feature")); assertEquals(123456l, preConflict.getAttribute("cat")); } /** * Checks the changes in PostDiffInitial.xml have all been applied successfully * @param restricted * @throws IOException */ void checkPostDiffInitialChanges( FeatureSource<SimpleFeatureType, SimpleFeature> restricted) throws IOException { // check from the datastore we actually have applied the diff SimpleFeature deleted = gss.getFeatureById(restricted, "restricted.d91fe390-bdc7-4b22-9316-2cd6c8737ef5"); assertNull(deleted); SimpleFeature updated = gss.getFeatureById(restricted, "restricted.be7cafea-d0b7-4257-9b9c-1ed3de3f7ef4"); assertNotNull(updated); assertEquals(-48l, updated.getAttribute("cat")); SimpleFeature added = gss.getFeatureById(restricted, "restricted.e9cba212-d79d-4569-aa0a-48f6b80539ee"); assertNotNull(added); assertEquals(123l, added.getAttribute("cat")); } }