/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geogig.geoserver.wfs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.Serializable;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.geoserver.catalog.Catalog;
import org.geoserver.catalog.CatalogFactory;
import org.geoserver.catalog.DataStoreInfo;
import org.geoserver.catalog.FeatureTypeInfo;
import org.geoserver.catalog.NamespaceInfo;
import org.geoserver.catalog.ProjectionPolicy;
import org.geoserver.catalog.WorkspaceInfo;
import org.geoserver.data.test.SystemTestData;
import org.geoserver.test.TestSetup;
import org.geoserver.test.TestSetupFrequency;
import org.geoserver.wfs.WFSTestSupport;
import org.geotools.data.DataAccess;
import org.geotools.data.FeatureSource;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.CRS;
import org.junit.Test;
import org.locationtech.geogig.cli.test.functional.CLITestContextBuilder;
import org.locationtech.geogig.geotools.data.GeoGigDataStore;
import org.locationtech.geogig.geotools.data.GeoGigDataStoreFactory;
import org.locationtech.geogig.model.NodeRef;
import org.locationtech.geogig.model.RevCommit;
import org.locationtech.geogig.plumbing.FindTreeChild;
import org.locationtech.geogig.plumbing.LsTreeOp;
import org.locationtech.geogig.plumbing.ResolveGeogigURI;
import org.locationtech.geogig.porcelain.CommitOp;
import org.locationtech.geogig.porcelain.LogOp;
import org.locationtech.geogig.repository.Context;
import org.locationtech.geogig.repository.impl.GeoGIG;
import org.locationtech.geogig.repository.impl.GlobalContextBuilder;
import org.locationtech.geogig.test.TestPlatform;
import org.locationtech.geogig.test.integration.RepositoryTestCase;
import org.opengis.feature.Feature;
import org.opengis.feature.type.FeatureType;
import org.w3c.dom.Document;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
@TestSetup(run = TestSetupFrequency.REPEAT)
public class WFSIntegrationTest extends WFSTestSupport {
private static final String NAMESPACE = "http://geogig.org";
private static final String WORKSPACE = "geogig";
private static final String STORE = "geogigstore";
private TestHelper helper;
private class TestHelper extends RepositoryTestCase {
@Override
protected Context createInjector() {
TestPlatform testPlatform = (TestPlatform) createPlatform();
GlobalContextBuilder.builder(new CLITestContextBuilder(testPlatform));
return GlobalContextBuilder.builder().build();
}
@Override
protected void setUpInternal() throws Exception {
configureGeogigDataStore();
}
File getRepositoryDirectory() {
return super.repositoryDirectory;
}
}
@Override
protected void setUpInternal(SystemTestData testData) throws Exception {
helper = new TestHelper();
helper.repositoryTempFolder.create();
helper.setUp();
}
@Override
protected void onTearDown(SystemTestData testData) throws Exception {
if (helper != null) {
helper.tearDown();
helper.repositoryTempFolder.delete();
}
}
@Override
protected void setUpTestData(SystemTestData testData) throws Exception {
// oevrride to avoid creating all the default feature types but call testData.setUp() only
// instead
testData.setUp();
}
@Override
protected void setUpNamespaces(Map<String, String> namespaces) {
namespaces.put(WORKSPACE, NAMESPACE);
}
private void configureGeogigDataStore() throws Exception {
helper.insertAndAdd(helper.lines1);
helper.getGeogig().command(CommitOp.class).call();
Catalog catalog = getCatalog();
CatalogFactory factory = catalog.getFactory();
NamespaceInfo ns = factory.createNamespace();
ns.setPrefix(WORKSPACE);
ns.setURI(NAMESPACE);
catalog.add(ns);
WorkspaceInfo ws = factory.createWorkspace();
ws.setName(ns.getName());
catalog.add(ws);
DataStoreInfo ds = factory.createDataStore();
ds.setEnabled(true);
ds.setDescription("Test Geogig DataStore");
ds.setName(STORE);
ds.setType(GeoGigDataStoreFactory.DISPLAY_NAME);
ds.setWorkspace(ws);
Map<String, Serializable> connParams = ds.getConnectionParameters();
Optional<URI> geogigDir = helper.getGeogig().command(ResolveGeogigURI.class).call();
File repositoryUrl = new File(geogigDir.get()).getParentFile();
assertTrue(repositoryUrl.exists() && repositoryUrl.isDirectory());
connParams.put(GeoGigDataStoreFactory.REPOSITORY.key, repositoryUrl);
connParams.put(GeoGigDataStoreFactory.DEFAULT_NAMESPACE.key, ns.getURI());
catalog.add(ds);
DataStoreInfo dsInfo = catalog.getDataStoreByName(WORKSPACE, STORE);
assertNotNull(dsInfo);
assertEquals(GeoGigDataStoreFactory.DISPLAY_NAME, dsInfo.getType());
DataAccess<? extends FeatureType, ? extends Feature> dataStore = dsInfo.getDataStore(null);
assertNotNull(dataStore);
assertTrue(dataStore instanceof GeoGigDataStore);
FeatureTypeInfo fti = factory.createFeatureType();
fti.setNamespace(ns);
fti.setCatalog(catalog);
fti.setStore(dsInfo);
fti.setSRS("EPSG:4326");
fti.setName("Lines");
fti.setAdvertised(true);
fti.setEnabled(true);
fti.setCqlFilter("INCLUDE");
fti.setProjectionPolicy(ProjectionPolicy.FORCE_DECLARED);
ReferencedEnvelope bounds = new ReferencedEnvelope(-180, 180, -90, 90,
CRS.decode("EPSG:4326"));
fti.setNativeBoundingBox(bounds);
fti.setLatLonBoundingBox(bounds);
catalog.add(fti);
fti = catalog.getFeatureType(fti.getId());
FeatureSource<? extends FeatureType, ? extends Feature> featureSource;
featureSource = fti.getFeatureSource(null, null);
assertNotNull(featureSource);
}
@Test
public void testInsert() throws Exception {
Document dom = insert();
assertEquals("wfs:TransactionResponse", dom.getDocumentElement().getNodeName());
assertEquals("1",
getFirstElementByTagName(dom, "wfs:totalInserted").getFirstChild().getNodeValue());
dom = getAsDOM(
"wfs?version=1.1.0&request=getfeature&typename=geogig:Lines&srsName=EPSG:4326&");
// print(dom);
assertEquals("wfs:FeatureCollection", dom.getDocumentElement().getNodeName());
assertEquals(2, dom.getElementsByTagName("geogig:Lines").getLength());
}
private Document insert() throws Exception {
String xml = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\" "//
+ " xmlns:wfs=\"http://www.opengis.net/wfs\" "//
+ " xmlns:gml=\"http://www.opengis.net/gml\" " //
+ " xmlns:geogig=\"" + NAMESPACE + "\">"//
+ "<wfs:Insert>"//
+ "<geogig:Lines gml:id=\"Lines.1000\">"//
+ " <geogig:sp>StringProp new</geogig:sp>"//
+ " <geogig:ip>999</geogig:ip>"//
+ " <geogig:pp>"//
+ " <gml:LineString srsDimension=\"2\" srsName=\"EPSG:4326\">"//
+ " <gml:posList>1.0 1.0 2.0 2.0</gml:posList>"//
+ " </gml:LineString>"//
+ " </geogig:pp>"//
+ "</geogig:Lines>"//
+ "</wfs:Insert>"//
+ "</wfs:Transaction>";
Document dom = postAsDOM("wfs", xml);
return dom;
}
@Test
public void testUpdate() throws Exception {
Document dom = update();
assertEquals("wfs:TransactionResponse", dom.getDocumentElement().getNodeName());
assertEquals("1",
getFirstElementByTagName(dom, "wfs:totalUpdated").getFirstChild().getNodeValue());
dom = getAsDOM("wfs?version=1.1.0&request=getfeature&typename=geogig:Lines" + "&"
+ "cql_filter=ip%3D1000");
assertEquals("wfs:FeatureCollection", dom.getDocumentElement().getNodeName());
assertEquals(1, dom.getElementsByTagName("geogig:Lines").getLength());
}
private Document update() throws Exception {
String xml = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\""//
+ " xmlns:geogig=\"" + NAMESPACE + "\""//
+ " xmlns:ogc=\"http://www.opengis.net/ogc\""//
+ " xmlns:gml=\"http://www.opengis.net/gml\""//
+ " xmlns:wfs=\"http://www.opengis.net/wfs\">"//
+ " <wfs:Update typeName=\"geogig:Lines\">"//
+ " <wfs:Property>"//
+ " <wfs:Name>geogig:pp</wfs:Name>"//
+ " <wfs:Value>"
+ " <gml:LineString srsDimension=\"2\" srsName=\"EPSG:4326\">"//
+ " <gml:posList>1 2 3 4</gml:posList>"//
+ " </gml:LineString>"//
+ " </wfs:Value>"//
+ " </wfs:Property>"//
+ " <ogc:Filter>"//
+ " <ogc:PropertyIsEqualTo>"//
+ " <ogc:PropertyName>ip</ogc:PropertyName>"//
+ " <ogc:Literal>1000</ogc:Literal>"//
+ " </ogc:PropertyIsEqualTo>"//
+ " </ogc:Filter>"//
+ " </wfs:Update>"//
+ "</wfs:Transaction>";
Document dom = postAsDOM("wfs", xml);
return dom;
}
/**
* Test case to expose issue https://github.com/boundlessgeo/geogig/issues/310
* "Editing Features changes the feature type"
*
* @see #testUpdateDoesntChangeFeatureType()
*/
@Test
public void testInsertDoesntChangeFeatureType() throws Exception {
String xml = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\" "//
+ " xmlns:wfs=\"http://www.opengis.net/wfs\" "//
+ " xmlns:gml=\"http://www.opengis.net/gml\" " //
+ " xmlns:geogig=\"" + NAMESPACE + "\">"//
+ "<wfs:Insert>"//
+ "<geogig:Lines gml:id=\"Lines.1000\">"//
+ " <geogig:sp>added</geogig:sp>"//
+ " <geogig:ip>7</geogig:ip>"//
+ " <geogig:pp>"//
+ " <gml:LineString srsDimension=\"2\" srsName=\"EPSG:4326\">"//
+ " <gml:posList>1 2 3 4</gml:posList>"//
+ " </gml:LineString>"//
+ " </geogig:pp>"//
+ "</geogig:Lines>"//
+ "</wfs:Insert>"//
+ "</wfs:Transaction>";
GeoGIG geogig = helper.getGeogig();
final NodeRef initialTypeTreeRef = geogig.command(FindTreeChild.class).setChildPath("Lines")
.call().get();
assertFalse(initialTypeTreeRef.getMetadataId().isNull());
Document dom = postAsDOM("wfs", xml);
try {
assertEquals("wfs:TransactionResponse", dom.getDocumentElement().getNodeName());
} catch (AssertionError e) {
print(dom);
throw e;
}
try {
assertEquals("1", getFirstElementByTagName(dom, "wfs:totalInserted").getFirstChild()
.getNodeValue());
} catch (AssertionError e) {
print(dom);
throw e;
}
final NodeRef finalTypeTreeRef = geogig.command(FindTreeChild.class).setChildPath("Lines")
.call().get();
assertFalse(initialTypeTreeRef.equals(finalTypeTreeRef));
assertFalse(finalTypeTreeRef.getMetadataId().isNull());
assertEquals("Feature type tree metadataId shouuldn't change upon edits",
initialTypeTreeRef.getMetadataId(), finalTypeTreeRef.getMetadataId());
Iterator<NodeRef> featureRefs = geogig.command(LsTreeOp.class).setReference("Lines").call();
while (featureRefs.hasNext()) {
NodeRef ref = featureRefs.next();
assertEquals(finalTypeTreeRef.getMetadataId(), ref.getMetadataId());
assertFalse(ref.toString(), ref.getNode().getMetadataId().isPresent());
}
}
/**
* Test case to expose issue https://github.com/boundlessgeo/geogig/issues/310
* "Editing Features changes the feature type"
*
* @see #testInsertDoesntChangeFeatureType()
*/
@Test
public void testUpdateDoesntChangeFeatureType() throws Exception {
String xml = "<wfs:Transaction service=\"WFS\" version=\"1.1.0\""//
+ " xmlns:geogig=\"" + NAMESPACE + "\""//
+ " xmlns:ogc=\"http://www.opengis.net/ogc\""//
+ " xmlns:gml=\"http://www.opengis.net/gml\""//
+ " xmlns:wfs=\"http://www.opengis.net/wfs\">"//
+ " <wfs:Update typeName=\"geogig:Lines\">"//
+ " <wfs:Property>"//
+ " <wfs:Name>geogig:pp</wfs:Name>"//
+ " <wfs:Value>"
+ " <gml:LineString srsDimension=\"2\" srsName=\"EPSG:4326\">"//
+ " <gml:posList>3 4 5 6</gml:posList>"//
+ " </gml:LineString>"//
+ " </wfs:Value>"//
+ " </wfs:Property>"//
+ " <ogc:Filter>"//
+ " <ogc:PropertyIsEqualTo>"//
+ " <ogc:PropertyName>ip</ogc:PropertyName>"//
+ " <ogc:Literal>1000</ogc:Literal>"//
+ " </ogc:PropertyIsEqualTo>"//
+ " </ogc:Filter>"//
+ " </wfs:Update>"//
+ "</wfs:Transaction>";
GeoGIG geogig = helper.getGeogig();
final NodeRef initialTypeTreeRef = geogig.command(FindTreeChild.class).setChildPath("Lines")
.call().get();
assertFalse(initialTypeTreeRef.getMetadataId().isNull());
Document dom = postAsDOM("wfs", xml);
assertEquals("wfs:TransactionResponse", dom.getDocumentElement().getNodeName());
assertEquals("1",
getFirstElementByTagName(dom, "wfs:totalUpdated").getFirstChild().getNodeValue());
final NodeRef finalTypeTreeRef = geogig.command(FindTreeChild.class).setChildPath("Lines")
.call().get();
assertFalse(initialTypeTreeRef.equals(finalTypeTreeRef));
assertFalse(finalTypeTreeRef.getMetadataId().isNull());
assertEquals("Feature type tree metadataId shouuldn't change upon edits",
initialTypeTreeRef.getMetadataId(), finalTypeTreeRef.getMetadataId());
Iterator<NodeRef> featureRefs = geogig.command(LsTreeOp.class).setReference("Lines").call();
while (featureRefs.hasNext()) {
NodeRef ref = featureRefs.next();
assertEquals(finalTypeTreeRef.getMetadataId(), ref.getMetadataId());
assertFalse(ref.toString(), ref.getNode().getMetadataId().isPresent());
}
}
@Test
public void testCommitsSurviveShutDown() throws Exception {
GeoGIG geogig = helper.getGeogig();
insert();
update();
List<RevCommit> expected = ImmutableList.copyOf(geogig.command(LogOp.class).call());
File repoDir = helper.getRepositoryDirectory();
assertTrue(repoDir.exists() && repoDir.isDirectory());
// shut down server
destroyGeoServer();
TestPlatform testPlatform = new TestPlatform(repoDir);
Context context = new CLITestContextBuilder(testPlatform).build();
geogig = new GeoGIG(context);
try {
assertNotNull(geogig.getRepository());
List<RevCommit> actual = ImmutableList.copyOf(geogig.command(LogOp.class).call());
assertEquals(expected, actual);
} finally {
geogig.close();
}
}
}