/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, 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.geotools.arcsde.data;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import junit.framework.AssertionFailedError;
import org.geotools.arcsde.ArcSdeException;
import org.geotools.arcsde.session.Command;
import org.geotools.arcsde.session.ISession;
import org.geotools.arcsde.session.ISessionPool;
import org.geotools.arcsde.session.SdeRow;
import org.geotools.data.BatchFeatureEvent;
import org.geotools.data.DataStore;
import org.geotools.data.DataUtilities;
import org.geotools.data.DefaultQuery;
import org.geotools.data.DefaultTransaction;
import org.geotools.data.FeatureEvent;
import org.geotools.data.FeatureListener;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureSource;
import org.geotools.data.FeatureStore;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
import org.geotools.data.FeatureEvent.Type;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.filter.text.cql2.CQL;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.opengis.feature.Attribute;
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureVisitor;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.GeometryDescriptor;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Id;
import org.opengis.filter.identity.FeatureId;
import com.esri.sde.sdk.client.SeConnection;
import com.esri.sde.sdk.client.SeDBMSInfo;
import com.esri.sde.sdk.client.SeException;
import com.esri.sde.sdk.client.SeQuery;
import com.esri.sde.sdk.client.SeSqlConstruct;
import com.esri.sde.sdk.client.SeTable;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.io.WKTReader;
/**
* Unit tests for transaction support
*
* @author Gabriel Roldan, Axios Engineering
* @source $URL:
* http://svn.geotools.org/geotools/trunk/gt/modules/plugin/arcsde/da/src/test/java/org
* /geotools/arcsde/data/ArcSDEFeatureStoreTest.java $
* @version $Id$
*/
public class ArcSDEFeatureStoreTest {
/** package logger */
private static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger(ArcSDEFeatureStoreTest.class.getPackage().getName());
private TestData testData;
/**
* Flag that indicates whether the underlying database is MS SQL Server.
* <p>
* This is used to decide what's the expected result count in some transaction tests, and its
* value is obtained from an {@link SeDBMSInfo} object. Hacky as it seems it is. The problem is
* that ArcSDE for SQL Server _explicitly_ sets the transaction isolation level to READ
* UNCOMMITTED for all and every transaction, while for other databases it uses READ COMMITTED.
* And no, ESRI documentation says there's no way to change nor workaround this behaviour.
* </p>
*/
private static boolean databaseIsMsSqlServer;
@Before
public void setUp() throws Exception {
testData = new TestData();
testData.setUp();
// do not insert test data, will do it at each test case
final boolean insertTestData = false;
testData.createTempTable(insertTestData);
ISession session = testData.getConnectionPool().getSession();
try {
SeDBMSInfo dbInfo = session.getDBMSInfo();
databaseIsMsSqlServer = dbInfo.dbmsId == SeDBMSInfo.SE_DBMS_IS_SQLSERVER;
} finally {
session.dispose();
}
}
@After
public void tearDown() {
boolean cleanTestTable = false;
boolean cleanPool = true;
testData.tearDown(cleanTestTable, cleanPool);
}
/**
* This is an adaptation of a classic MemoryDataStore test to match our sample data.
* <p>
* This test is focused on two things:
* <ul>
* <li>Are the Transactions actually independent?
* <li>Do the correct feature event notifications get sent out
* </ul>
*/
@Ignore
@Test
public void testFeatureEventsAndTransactionsForDelete() throws Exception {
// We are going to start with ...
testData.insertTestData();
final DataStore dataStore = testData.getDataStore();
final String typeName = testData.getTempTableName();
final FeatureSource<SimpleFeatureType, SimpleFeature> origional = dataStore
.getFeatureSource(typeName);
TestFeatureListener listener = new TestFeatureListener();
origional.addFeatureListener(listener);
// we are going to use this feature source to check that the
// public Transaction.AUTO_COMMIT view of the world
// is as expected.
assertEquals(8, origional.getCount(Query.ALL));
final SortedSet<String> allFids = new TreeSet<String>();
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = origional.getFeatures();
TestProgressListener progress = new TestProgressListener();
collection.accepts(new FeatureVisitor() {
public void visit(Feature feature) {
allFids.add(feature.getIdentifier().getID());
}
}, progress);
assertTrue("visitor completed", progress.completed);
assertEquals("visitor 100%", 100f, progress.progress);
assertNull("visitor no problems", progress.exception);
// we are going to use this transaction to modify and commit
DefaultTransaction t1 = new DefaultTransaction("Transaction 1");
FeatureStore<SimpleFeatureType, SimpleFeature> featureStore1 = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore
.getFeatureSource(typeName);
featureStore1.setTransaction(t1);
TestFeatureListener listener1 = new TestFeatureListener();
featureStore1.addFeatureListener(listener1);
// we are going to use this transaction to modify and rollback
DefaultTransaction t2 = new DefaultTransaction("Transaction 2");
FeatureStore<SimpleFeatureType, SimpleFeature> featureStore2 = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore
.getFeatureSource(typeName);
featureStore2.setTransaction(t2);
TestFeatureListener listener2 = new TestFeatureListener();
featureStore2.addFeatureListener(listener2);
// verify they are all working
assertEquals(8, origional.getCount(Query.ALL));
assertEquals(8, featureStore1.getCount(Query.ALL));
assertEquals(8, featureStore2.getCount(Query.ALL));
DefaultQuery queryOneFeature = new DefaultQuery();
queryOneFeature.setTypeName(typeName);
queryOneFeature.setFilter(Filter.INCLUDE);
queryOneFeature.setMaxFeatures(1);
queryOneFeature.setPropertyNames(Query.ALL_NAMES);
collection = featureStore1.getFeatures(queryOneFeature);
progress.reset();
final SortedSet<String> fids = new TreeSet<String>();
collection.accepts(new FeatureVisitor() {
public void visit(Feature feature) {
fids.add(feature.getIdentifier().getID());
}
}, progress);
assertTrue("visitor completed", progress.completed);
assertEquals("visitor 100%", 100f, progress.progress);
assertNull("visitor no problems", progress.exception);
assertEquals(1, fids.size());
String featureId = fids.first();
assertTrue(allFids.contains(featureId));
FilterFactory2 ff = CommonFactoryFinder.getFilterFactory2(null);
Filter filterOne = ff.id(Collections.singleton(ff.featureId(featureId)));
assertEquals("no events on AUTO_COMMIT", 0, listener.list.size());
assertEquals("no events on transaction1", 0, listener1.list.size());
assertEquals("no events on transaction2", 0, listener2.list.size());
featureStore1.removeFeatures(filterOne);
assertEquals("no events on AUTO_COMMIT", 0, listener.list.size());
assertEquals("single event on transaction2", 1, listener1.list.size());
assertEquals("no events on AUTO_COMMIT", 0, listener2.list.size());
FeatureEvent e = listener1.list.get(0);
assertEquals(featureStore1, e.getFeatureSource());
Id id = (Id) e.getFilter();
assertNotNull(id);
assertNotNull(id.getIDs());
assertTrue(id.getIDs().contains(featureId));
ReferencedEnvelope bounds = e.getBounds();
assertFalse(bounds.isEmpty());
assertFalse(bounds.isNull());
t1.commit();
assertEquals("commit event sent to AUTO_COMMIT", 1, listener.list.size());
assertEquals("commit event sent to transaction 1", 2, listener1.list.size());
assertEquals("commit event sent to transaction 2", listener2.list.size());
}
@Test
@Ignore
public void testFeatureEventsAndTransactionsForAdd() throws Exception {
// We are going to start with ...
testData.insertTestData();
final DataStore dataStore = testData.getDataStore();
final String typeName = testData.getTempTableName();
final FeatureSource<SimpleFeatureType, SimpleFeature> origional = dataStore
.getFeatureSource(typeName);
TestFeatureListener listener = new TestFeatureListener();
origional.addFeatureListener(listener);
// we are going to use this feature source to check that the
// public Transaction.AUTO_COMMIT view of the world
// is as expected.
assertEquals(8, origional.getCount(Query.ALL));
// we are going to use this transaction to modify and commit
DefaultTransaction t1 = new DefaultTransaction("Transaction 1");
FeatureStore<SimpleFeatureType, SimpleFeature> featureStore1 = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore
.getFeatureSource(typeName);
featureStore1.setTransaction(t1);
TestFeatureListener listener1 = new TestFeatureListener();
featureStore1.addFeatureListener(listener1);
// verify they are all working
assertEquals(8, origional.getCount(Query.ALL));
assertEquals(8, featureStore1.getCount(Query.ALL));
SimpleFeatureType schema = origional.getSchema();
SimpleFeatureBuilder build = new SimpleFeatureBuilder(schema);
int value = 24;
build.add(Integer.valueOf(value));
build.add(Short.valueOf((short) value));
build.add(new Float(value / 10.0F));
build.add(new Double(value / 10D));
build.add("FEATURE_" + value);
Calendar cal = Calendar.getInstance();
cal.set(2004, 06, value, 0, 0, 0);
build.add(cal);
WKTReader reader = new WKTReader();
build.add(reader.read("POINT(1 1)"));
SimpleFeature newFeature = build.buildFeature(null);
FeatureCollection newFeatures = DataUtilities.collection(newFeature);
List<FeatureId> newFids = featureStore1.addFeatures(newFeatures);
assertEquals(0, listener.list.size());
assertEquals(1, listener1.list.size());
FeatureEvent e = listener1.list.get(0);
Id id = (Id) e.getFilter();
assertTrue(id.getIdentifiers().containsAll(newFids));
// remember the FeatureId with a strong reference
FeatureId tempFeatureId = (FeatureId) id.getIdentifiers().iterator().next();
assertTrue(newFids.contains(tempFeatureId));
t1.commit();
assertEquals(1, listener.list.size());
assertEquals(2, listener1.list.size());
BatchFeatureEvent batch = (BatchFeatureEvent) listener1.list.get(2);
assertFalse("confirm tempFid is not in the commit", id.getIdentifiers().contains(
tempFeatureId));
assertNotNull(batch.getFilter());
FeatureId featureId = (FeatureId) batch.getCreatedFeatureIds().iterator().next();
String fid = featureId.getID();
assertSame("confirm temp feature Id was updated", tempFeatureId, featureId);
}
@Test
public void testDeleteByFIDAutoCommit() throws Exception {
testData.insertTestData();
final DataStore ds = testData.getDataStore();
final String typeName = testData.getTempTableName();
final String fid;
final Filter fidFilter;
{
// get a fid
DefaultQuery query = new DefaultQuery(typeName);
FeatureReader<SimpleFeatureType, SimpleFeature> reader = ds.getFeatureReader(query,
Transaction.AUTO_COMMIT);
try {
fid = reader.next().getID();
} finally {
reader.close();
}
final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
Set<FeatureId> ids = new HashSet<FeatureId>();
ids.add(ff.featureId(fid));
fidFilter = ff.id(ids);
}
{
FeatureWriter<SimpleFeatureType, SimpleFeature> writer;
writer = ds.getFeatureWriter(typeName, fidFilter, Transaction.AUTO_COMMIT);
try {
assertTrue(writer.hasNext());
SimpleFeature feature = writer.next();
assertEquals(fid, feature.getID());
writer.remove();
assertFalse(writer.hasNext());
} finally {
writer.close();
}
}
ISessionPool connectionPool = testData.getConnectionPool();
ISession session = connectionPool.getSession();
SeQuery seQuery;
try {
int objectId = (int) ArcSDEAdapter.getNumericFid(fid);
final String whereClause = "ROW_ID=" + objectId;
seQuery = session.issue(new Command<SeQuery>() {
@Override
public SeQuery execute(ISession session, SeConnection connection)
throws SeException, IOException {
SeQuery seQuery = new SeQuery(connection, new String[] { "ROW_ID", "INT32_COL",
"STRING_COL" }, new SeSqlConstruct(typeName, whereClause));
seQuery.prepareQuery();
seQuery.execute();
return seQuery;
}
});
SdeRow row = session.fetch(seQuery);
assertNull(row);
} finally {
session.dispose();
}
// was it really removed?
{
DefaultQuery query = new DefaultQuery(typeName, fidFilter);
FeatureReader<SimpleFeatureType, SimpleFeature> reader = ds.getFeatureReader(query,
Transaction.AUTO_COMMIT);
try {
assertFalse(reader.hasNext());
} finally {
reader.close();
}
}
}
/**
* Tests that all the features that match a filter based on attribute only filters (aka non
* spatial filters), are deleted correctly. This test assumes that there are no duplicate values
* in the test data.
*
* @throws Exception
*/
@Test
public void testDeleteByAttOnlyFilter() throws Exception {
testData.insertTestData();
final DataStore ds = testData.getDataStore();
final String typeName = testData.getTempTableName();
// get 2 features and build an OR'ed PropertyIsEqualTo filter
Filter or = CQL.toFilter("INT32_COL = 1 OR INT32_COL = 2");
FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriter(typeName, or,
Transaction.AUTO_COMMIT);
try {
assertTrue(writer.hasNext());
SimpleFeature feature = writer.next();
assertEquals(Integer.valueOf(1), feature.getAttribute("INT32_COL"));
writer.remove();
feature = writer.next();
assertEquals(Integer.valueOf(2), feature.getAttribute("INT32_COL"));
writer.remove();
assertFalse(writer.hasNext());
} finally {
writer.close();
}
// was it really removed?
FeatureReader<SimpleFeatureType, SimpleFeature> read = ds.getFeatureReader(
new DefaultQuery(typeName, or), Transaction.AUTO_COMMIT);
try {
assertFalse(read.hasNext());
} finally {
read.close();
}
}
@Test
public void testInsertAutoCommit() throws Exception {
// the table populated here is test friendly since it can hold
// any kind of geometries.
testData.truncateTempTable();
// there are some commented out just because the server I'm hitting
// is slow, not because they don't work. Feel free to uncomment.
testInsertAutoCommit(Geometry.class);
testInsertAutoCommit(Point.class);
testInsertAutoCommit(MultiPoint.class);
testInsertAutoCommit(LineString.class);
testInsertAutoCommit(MultiLineString.class);
testInsertAutoCommit(Polygon.class);
testInsertAutoCommit(MultiPolygon.class);
}
/**
* Add features to a FeatureWriter with a {@link Transaction} and ensure if the transaction was
* not committed, a request gets no features, and when the transaction is committed the query
* returns it.
*
* @throws Exception
*/
@Test
public void testInsertTransaction() throws Exception {
// start with an empty table
testData.truncateTempTable();
final String typeName = testData.getTempTableName();
final int featureCount = 2;
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures;
testFeatures = testData.createTestFeatures(LineString.class, featureCount);
final DataStore ds = testData.getDataStore();
final SimpleFeatureType ftype = testFeatures.getSchema();
final FeatureIterator<SimpleFeature> iterator = testFeatures.features();
final Transaction transaction = new DefaultTransaction();
final FeatureWriter<SimpleFeatureType, SimpleFeature> writer;
writer = ds.getFeatureWriter(typeName, transaction);
FeatureReader<SimpleFeatureType, SimpleFeature> reader;
final Query query = new DefaultQuery(typeName);
try {
try {
while (iterator.hasNext()) {
SimpleFeature addFeature = iterator.next();
SimpleFeature newFeature = writer.next();
for (int i = 0; i < ftype.getAttributeCount(); i++) {
String localName = ftype.getDescriptor(i).getLocalName();
newFeature.setAttribute(localName, addFeature.getAttribute(localName));
}
writer.write();
}
} catch (Exception e) {
transaction.rollback();
transaction.close();
} finally {
writer.close();
}
reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
boolean hasNext;
try {
hasNext = reader.hasNext();
} finally {
reader.close();
}
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertTrue(hasNext);
} else {
assertFalse("Features added, transaction not commited", hasNext);
}
try {
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
}
} finally {
transaction.close();
}
try {
reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
for (int i = 0; i < featureCount; i++) {
assertTrue(reader.hasNext());
reader.next();
}
assertFalse(reader.hasNext());
} finally {
reader.close();
}
}
@Test
public void testInsertTransactionAndQueryByFid() throws Exception {
// start with an empty table
final String typeName = testData.getTempTableName();
final int featureCount = 2;
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures;
testFeatures = testData.createTestFeatures(LineString.class, featureCount);
final DataStore ds = testData.getDataStore();
final FeatureStore<SimpleFeatureType, SimpleFeature> fStore;
fStore = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds.getFeatureSource(typeName);
final Transaction transaction = new DefaultTransaction("testInsertTransactionAndQueryByFid");
fStore.setTransaction(transaction);
try {
final List<FeatureId> addedFids = fStore.addFeatures(testFeatures);
final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
final Set<FeatureId> fids = new HashSet<FeatureId>();
for (FeatureId fid : addedFids) {
fids.add(fid);
}
final Id newFidsFilter = ff.id(fids);
FeatureCollection<SimpleFeatureType, SimpleFeature> features;
features = fStore.getFeatures(newFidsFilter);
assertEquals(2, features.size());
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
transaction.close();
}
}
/**
* Make sure features are validated against it's schema before being added
*/
@Test
public void testInsertNonNillableAttributeCheck() throws Exception {
// start with an empty table
testData.truncateTempTable();
final String typeName = testData.getTempTableName();
final int featureCount = 1;
SimpleFeature feature;
feature = testData.createTestFeatures(LineString.class, featureCount).features().next();
final DataStore ds = testData.getDataStore();
FeatureStore<SimpleFeatureType, SimpleFeature> store = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds
.getFeatureSource(typeName);
SimpleFeatureType ftype = store.getSchema();
assertFalse(ftype.getDescriptor("INT32_COL").isNillable());
feature.setAttribute("INT32_COL", null);
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = DataUtilities
.collection(feature);
try {
store.addFeatures(collection);
fail("Expected IAE");
} catch (IllegalArgumentException e) {
// note this should really be org.opengis.feature.IllegalAttributeException but
// Types.validate throws IllegalArgumentException instead
assertTrue(true);
}
}
@Test
public void testUpdateAutoCommit() throws Exception {
testData.insertTestData();
final String typeName = testData.getTempTableName();
final DataStore ds = testData.getDataStore();
final Filter filter = CQL.toFilter("INT32_COL = 3");
final FeatureWriter<SimpleFeatureType, SimpleFeature> writer;
writer = ds.getFeatureWriter(typeName, filter, Transaction.AUTO_COMMIT);
try {
assertTrue(writer.hasNext());
SimpleFeature feature = writer.next();
feature.setAttribute("INT32_COL", Integer.valueOf(-1000));
writer.write();
assertFalse(writer.hasNext());
} finally {
writer.close();
}
DefaultQuery query = new DefaultQuery(typeName, filter);
FeatureReader<SimpleFeatureType, SimpleFeature> reader;
reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
try {
assertFalse(reader.hasNext());
} finally {
reader.close();
}
query = new DefaultQuery(typeName, CQL.toFilter("INT32_COL = -1000"));
reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
try {
assertTrue(reader.hasNext());
reader.next();
assertFalse(reader.hasNext());
} finally {
reader.close();
}
}
@Test
public void testUpdateTransaction() throws Exception {
testData.insertTestData();
final String typeName = testData.getTempTableName();
final DataStore ds = testData.getDataStore();
final Filter oldValueFilter = CQL.toFilter("INT32_COL = 3");
final Query oldValueQuery = new DefaultQuery(typeName, oldValueFilter);
final Filter newValueFilter = CQL.toFilter("INT32_COL = -1000");
final Query newValueQuery = new DefaultQuery(typeName, newValueFilter);
final Transaction transaction = new DefaultTransaction("testUpdateTransaction");
FeatureWriter<SimpleFeatureType, SimpleFeature> writer;
writer = ds.getFeatureWriter(typeName, oldValueFilter, transaction);
FeatureReader<SimpleFeatureType, SimpleFeature> reader;
try {
try {
assertTrue(writer.hasNext());
SimpleFeature feature = writer.next();
feature.setAttribute("INT32_COL", Integer.valueOf(-1000));
writer.write();
assertFalse(writer.hasNext());
} finally {
writer.close();
}
reader = ds.getFeatureReader(oldValueQuery, Transaction.AUTO_COMMIT);
try {
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertFalse(reader.hasNext());
} else {
assertTrue(reader.hasNext());
}
} finally {
reader.close();
}
reader = ds.getFeatureReader(newValueQuery, Transaction.AUTO_COMMIT);
try {
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertTrue(reader.hasNext());
} else {
assertFalse(reader.hasNext());
}
} finally {
reader.close();
}
reader = ds.getFeatureReader(oldValueQuery, transaction);
try {
assertFalse(reader.hasNext());
} finally {
reader.close();
}
reader = ds.getFeatureReader(newValueQuery, transaction);
try {
assertTrue(reader.hasNext());
} finally {
reader.close();
}
try {
transaction.commit();
} catch (IOException e) {
transaction.rollback();
throw e;
}
} finally {
transaction.close();
}
reader = ds.getFeatureReader(newValueQuery, Transaction.AUTO_COMMIT);
try {
assertTrue(reader.hasNext());
} finally {
reader.close();
}
}
@Test
public void testModifyFeaturesTransaction() throws Exception {
testData.insertTestData();
final String typeName = testData.getTempTableName();
final DataStore ds = testData.getDataStore();
final FeatureStore<SimpleFeatureType, SimpleFeature> store;
store = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds.getFeatureSource(typeName);
final SimpleFeatureType schema = store.getSchema();
final Filter oldValueFilter = CQL.toFilter("INT32_COL = 3");
final Filter newValueFilter = CQL.toFilter("INT32_COL = -1000");
FeatureCollection<SimpleFeatureType, SimpleFeature> features = store
.getFeatures(oldValueFilter);
final int initialSize = features.size();
assertEquals(1, initialSize);// just to not go forward with bad data
final SimpleFeature originalFeature;
FeatureIterator<SimpleFeature> iterator = features.features();
try {
originalFeature = iterator.next();
} finally {
iterator.close();
}
{
final Transaction transaction = new DefaultTransaction("testModifyFeaturesTransaction");
store.setTransaction(transaction);
try {
final AttributeDescriptor propDescriptor = schema.getDescriptor("INT32_COL");
store.modifyFeatures(propDescriptor, Integer.valueOf(-1000), oldValueFilter);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
} finally {
transaction.close();
}
}
store.setTransaction(Transaction.AUTO_COMMIT);
final Query oldValueQuery = new DefaultQuery(typeName, oldValueFilter);
final Query newValueQuery = new DefaultQuery(typeName, newValueFilter);
assertEquals(0, store.getCount(oldValueQuery));
assertEquals(1, store.getCount(newValueQuery));
final FeatureIterator<SimpleFeature> newFeatures = store.getFeatures(newValueQuery)
.features();
final SimpleFeature modifiedFeature;
try {
modifiedFeature = newFeatures.next();
} finally {
newFeatures.close();
}
// verify the non modified properties stay the same
final List<Attribute> originalValues = (List<Attribute>) originalFeature.getValue();
final List<Attribute> actualValues = (List<Attribute>) modifiedFeature.getValue();
for (int i = 0; i < originalValues.size(); i++) {
Attribute originalAtt = originalValues.get(i);
Attribute actualAtt = actualValues.get(i);
Name name = originalAtt.getName();
// bah, date equals does not work, I don't care for this test
String localName = name.getLocalPart();
if (!"INT32_COL".equals(localName) && !"SHAPE".equals(localName)
&& !"DATE_COL".equals(localName)) {
assertEquals(name + " does not match", originalAtt.getValue(), actualAtt.getValue());
}
}
}
@Test
public void testUpdateAdjacentPolygonsTransaction() throws Exception {
final WKTReader reader = new WKTReader();
final Polygon p1 = (Polygon) reader
.read("POLYGON((-10 -10, -10 10, 0 10, 0 -10, -10 -10))");
final Polygon p2 = (Polygon) reader.read("POLYGON((0 -10, 0 10, 10 10, 10 -10, 0 -10))");
final Polygon modif1 = (Polygon) reader
.read("POLYGON ((-10 -10, -10 10, 5 10, -5 -10, -10 -10))");
final Polygon modif2 = (Polygon) reader
.read("POLYGON ((-5 -10, 5 10, 10 10, 10 -10, -5 -10))");
final String typeName = testData.getTempTableName(); // "SDE.CJ_TST_1";
final ArcSDEDataStore dataStore = testData.getDataStore();
// String[] typeNames = dataStore.getTypeNames();
// System.err.println(typeNames);
final FeatureStore<SimpleFeatureType, SimpleFeature> store;
store = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore
.getFeatureSource(typeName);
final SimpleFeatureType schema = store.getSchema();
GeometryDescriptor defaultGeometry = schema.getGeometryDescriptor();
String fid1;
String fid2;
// insert polygons p1, p2 and grab the fids for later retrieval
{
FeatureWriter<SimpleFeatureType, SimpleFeature> writer = dataStore
.getFeatureWriterAppend(typeName, Transaction.AUTO_COMMIT);
SimpleFeature feature;
try {
feature = writer.next();
// set this attribute as its the only non nillable one
feature.setAttribute("INT32_COL", Integer.valueOf(0));
// now set the geometry
feature.setAttribute(defaultGeometry.getName(), p1);
writer.write();
fid1 = feature.getID();
feature = writer.next();
// set this attribute as its the only non nillable one
feature.setAttribute("INT32_COL", Integer.valueOf(0));
// now set the geometry
feature.setAttribute(defaultGeometry.getName(), p2);
writer.write();
fid2 = feature.getID();
} finally {
writer.close();
}
}
final Transaction transaction = new DefaultTransaction("testUpdateAdjacentPolygons");
store.setTransaction(transaction);
final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
Filter fid1Filter = ff.id(Collections.singleton(ff.featureId(fid1)));
Filter fid2Filter = ff.id(Collections.singleton(ff.featureId(fid2)));
try {
store.modifyFeatures(defaultGeometry, modif2, fid2Filter);
store.modifyFeatures(defaultGeometry, modif1, fid1Filter);
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
transaction.close();
}
store.setTransaction(Transaction.AUTO_COMMIT);
try {
FeatureCollection<SimpleFeatureType, SimpleFeature> features;
FeatureIterator<SimpleFeature> iterator;
features = store.getFeatures(fid1Filter);
iterator = features.features();
final SimpleFeature feature1 = iterator.next();
iterator.close();
features = store.getFeatures(fid2Filter);
iterator = features.features();
final SimpleFeature feature2 = iterator.next();
iterator.close();
// Note that for tables that are ambiguous about what types of geometries
// they store (as this table is), ArcSDE will "compress" a stored geometry
// to it's simplest representation. So in case the defaultGeometry.getBinding()
// returns "Geometry", do instanceof checks to verify what kind of geometry
// you're getting back
Geometry actual1 = (Geometry) feature1.getAttribute(defaultGeometry.getLocalName());
Geometry actual2 = (Geometry) feature2.getAttribute(defaultGeometry.getLocalName());
System.out.println(actual1);
System.out.println(modif1);
// there's some rounding that goes on inside SDE. Need to do some simple buffering to
// make sure
// we're not getting rounding errors
assertTrue(modif1.buffer(.01).contains(actual1));
assertTrue(modif2.buffer(.01).contains(actual2));
} finally {
try {
store.removeFeatures(fid1Filter);
store.removeFeatures(fid2Filter);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
}
/**
* Tests the writing of features with autocommit transaction.
*
* @param geometryClass
* DOCUMENT ME!
* @throws Exception
* DOCUMENT ME!
* @throws IllegalArgumentException
* DOCUMENT ME!
*/
private void testInsertAutoCommit(Class<? extends Geometry> geometryClass) throws Exception {
final String typeName = testData.getTempTableName();
final int insertCount = 2;
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures;
testFeatures = testData.createTestFeatures(geometryClass, insertCount);
final DataStore ds = testData.getDataStore();
final FeatureSource<SimpleFeatureType, SimpleFeature> fsource = ds
.getFeatureSource(typeName);
// incremented on each feature added event to
// ensure events are being raised as expected
// (the count is wraped inside an array to be able of declaring
// the variable as final and accessing it from inside the anonymous
// inner class)
/*
* final int[] featureAddedEventCount = { 0 };
*
* fsource.addFeatureListener(new FeatureListener() { public void changed(FeatureEvent evt)
* { if (evt.getEventType() != FeatureEvent.FEATURES_ADDED) { throw new
* IllegalArgumentException( "Expected FEATURES_ADDED event, got " + evt.getEventType()); }
*
* ++featureAddedEventCount[0]; } });
*/
final int initialCount = fsource.getCount(Query.ALL);
FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriterAppend(
typeName, Transaction.AUTO_COMMIT);
SimpleFeature source;
SimpleFeature dest;
try {
for (FeatureIterator<SimpleFeature> fi = testFeatures.features(); fi.hasNext();) {
source = fi.next();
dest = writer.next();
dest.setAttributes(source.getAttributes());
writer.write();
}
} finally {
writer.close();
}
// was the features really inserted?
int fcount = fsource.getCount(Query.ALL);
assertEquals(testFeatures.size() + initialCount, fcount);
/*
* String msg = "a FEATURES_ADDED event should have been called " + features.size() + "
* times"; assertEquals(msg, features.size(), featureAddedEventCount[0]);
*/
}
@Test
public void testWriteAndUpdateNullShapes() throws Exception {
final String typeName = testData.getTempTableName();
testData.truncateTempTable();
DataStore ds = testData.getDataStore();
SimpleFeature feature;
FeatureWriter<SimpleFeatureType, SimpleFeature> writer;
writer = ds.getFeatureWriter(typeName, Transaction.AUTO_COMMIT);
try {
feature = writer.next();
feature.setAttribute("INT32_COL", Integer.valueOf(1000));
writer.write();
} finally {
writer.close();
}
LOGGER.info("Wrote null-geom feature to sde");
FeatureReader<SimpleFeatureType, SimpleFeature> reader;
reader = ds.getFeatureReader(new DefaultQuery(typeName, Filter.INCLUDE),
Transaction.AUTO_COMMIT);
// save the ID to update the feature later
String newId;
try {
assertTrue(reader.hasNext());
feature = reader.next();
LOGGER.info("recovered geometry " + feature.getDefaultGeometry()
+ " from single inserted feature.");
assertNull(feature.getDefaultGeometry());
newId = feature.getID();
assertFalse(reader.hasNext());
} finally {
reader.close();
}
LOGGER.info("Confirmed exactly one feature in new sde layer");
FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
HashSet<FeatureId> ids = new HashSet<FeatureId>();
ids.add(ff.featureId(newId));
Filter idFilter = ff.id(ids);
writer = ds.getFeatureWriter(typeName, idFilter, Transaction.AUTO_COMMIT);
try {
assertTrue(writer.hasNext());
LOGGER.info("Confirmed feature is fetchable via it's api-determined FID");
GeometryFactory gf = new GeometryFactory();
int index = 10;
Coordinate[] coords1 = { new Coordinate(0, 0), new Coordinate(++index, ++index) };
Coordinate[] coords2 = { new Coordinate(0, index), new Coordinate(index, 0) };
LineString[] lines = { gf.createLineString(coords1), gf.createLineString(coords2) };
MultiLineString sampleMultiLine = gf.createMultiLineString(lines);
SimpleFeature toBeUpdated = writer.next();
toBeUpdated.setAttribute("SHAPE", sampleMultiLine);
writer.write();
} finally {
writer.close();
}
LOGGER.info("Null-geom feature updated with a sample geometry.");
DefaultQuery query = new DefaultQuery(testData.getTempTableName(), idFilter);
reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
try {
assertTrue(reader.hasNext());
feature = reader.next();
MultiLineString recoveredMLS = (MultiLineString) feature.getDefaultGeometry();
assertTrue(!recoveredMLS.isEmpty());
// I tried to compare the recovered MLS to the
// sampleMultiLineString, but they're
// slightly different. SDE does some rounding, and winds up giving
// me 0.0000002 for zero,
// and 11.9992 for 12. Meh.
} finally {
reader.close();
}
}
/**
* Tests the writing of features with real transactions
*
* @throws UnsupportedOperationException
* DOCUMENT ME!
*/
@Test
public void testFeatureWriterTransaction() throws Exception {
// the table populated here is test friendly since it can hold
// any kind of geometries.
testData.insertTestData();
final String typeName = testData.getTempTableName();
final DataStore ds = testData.getDataStore();
final FeatureSource<SimpleFeatureType, SimpleFeature> fsource = ds
.getFeatureSource(typeName);
final int initialCount = fsource.getCount(Query.ALL);
final int writeCount = initialCount + 2;
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures = testData
.createTestFeatures(LineString.class, writeCount);
// incremented on each feature added event to
// ensure events are being raised as expected
// (the count is wraped inside an array to be able of declaring
// the variable as final and accessing it from inside the anonymous
// inner class)
// final int[] featureAddedEventCount = { 0 };
final Transaction transaction = new DefaultTransaction();
final FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriter(
typeName, Filter.INCLUDE, transaction);
SimpleFeature source;
SimpleFeature dest;
int count = 0;
try {
for (FeatureIterator<SimpleFeature> fi = testFeatures.features(); fi.hasNext(); count++) {
if (count < initialCount) {
assertTrue("at index " + count, writer.hasNext());
} else {
assertFalse("at index " + count, writer.hasNext());
}
source = fi.next();
dest = writer.next();
dest.setAttributes(source.getAttributes());
writer.write();
}
transaction.commit();
} catch (Exception e) {
transaction.rollback();
throw e;
} finally {
writer.close();
transaction.close();
}
// was the features really inserted?
int fcount = fsource.getCount(Query.ALL);
assertEquals(writeCount, fcount);
/*
* String msg = "a FEATURES_ADDED event should have been called " + features.size() + "
* times"; assertEquals(msg, features.size(), featureAddedEventCount[0]);
*/
}
/**
* DOCUMENT ME!
*
* @throws UnsupportedOperationException
* DOCUMENT ME!
*/
@Test
public void testFeatureWriterAppend() throws Exception {
// the table populated here is test friendly since it can hold
// any kind of geometries.
testData.insertTestData();
final String typeName = testData.getTempTableName();
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures = testData
.createTestFeatures(LineString.class, 2);
final DataStore ds = testData.getDataStore();
final FeatureSource<SimpleFeatureType, SimpleFeature> fsource = ds
.getFeatureSource(typeName);
final int initialCount = fsource.getCount(Query.ALL);
final FeatureWriter<SimpleFeatureType, SimpleFeature> writer = ds.getFeatureWriterAppend(
typeName, Transaction.AUTO_COMMIT);
SimpleFeature source;
SimpleFeature dest;
for (FeatureIterator<SimpleFeature> fi = testFeatures.features(); fi.hasNext();) {
assertFalse(writer.hasNext());
source = fi.next();
dest = writer.next();
dest.setAttributes(source.getAttributes());
writer.write();
}
writer.close();
// were the features really inserted?
int fcount = fsource.getCount(Query.ALL);
assertEquals(testFeatures.size() + initialCount, fcount);
}
/**
* Ensure modified features for a given FeatureStore are returned by subsequent queries even if
* the transaction has not been committed.
*
* @throws Exception
*/
@Test
public void testTransactionStateDiff() throws Exception {
testData.createTempTable(true);
// testData.insertTestData();
final DataStore ds = testData.getDataStore();
final String typeName = testData.getTempTableName();
final FeatureStore<SimpleFeatureType, SimpleFeature> transFs;
transFs = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds.getFeatureSource(typeName);
final SimpleFeatureType schema = transFs.getSchema();
// once the transaction is set to the FeatureStore, it lasts until
// another transaction
// is set. Calling transaction.close() closes Transaction.State
// held on it, allowing State objects to release resources. After
// close() the transaction
// is no longer valid.
final Transaction transaction = new DefaultTransaction("test_handle");
transFs.setTransaction(transaction);
try {
// create a feature to add
SimpleFeatureBuilder builder = new SimpleFeatureBuilder(schema);
builder.set("INT32_COL", Integer.valueOf(1000));
builder.set("STRING_COL", "inside transaction");
SimpleFeature feature = builder.buildFeature(null);
// add the feature
transFs.addFeatures(DataUtilities.collection(feature));
// now confirm for that transaction the feature is fetched, and outside
// it it's not.
final Filter filterNewFeature = CQL.toFilter("INT32_COL = 1000");
final DefaultQuery newFeatureQuery = new DefaultQuery(typeName, filterNewFeature);
FeatureCollection<SimpleFeatureType, SimpleFeature> features;
features = transFs.getFeatures(filterNewFeature);
int size = features.size();
assertEquals(1, size);
// ok transaction respected, assert the feature does not exist outside
// it (except is the db is MS SQL Server)
{
FeatureReader<SimpleFeatureType, SimpleFeature> autoCommitReader;
autoCommitReader = ds.getFeatureReader(newFeatureQuery, Transaction.AUTO_COMMIT);
try {
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertTrue(autoCommitReader.hasNext());
} else {
assertFalse(autoCommitReader.hasNext());
}
} finally {
autoCommitReader.close();
}
}
// ok, but what if we ask for a feature reader with the same transaction
{
FeatureReader<SimpleFeatureType, SimpleFeature> transactionReader;
transactionReader = ds.getFeatureReader(newFeatureQuery, transaction);
try {
assertTrue(transactionReader.hasNext());
transactionReader.next();
assertFalse(transactionReader.hasNext());
} finally {
transactionReader.close();
}
}
// now commit, and Transaction.AUTO_COMMIT should carry it over
// do not close the transaction, we'll keep using it
try {
transaction.commit();
} catch (IOException e) {
transaction.rollback();
throw e;
}
{
FeatureReader<SimpleFeatureType, SimpleFeature> autoCommitReader;
autoCommitReader = ds.getFeatureReader(newFeatureQuery, Transaction.AUTO_COMMIT);
try {
assertTrue(autoCommitReader.hasNext());
} finally {
autoCommitReader.close();
}
}
// now keep using the transaction, it should still work
transFs.removeFeatures(filterNewFeature);
// no features removed yet outside the transaction
{
FeatureReader<SimpleFeatureType, SimpleFeature> autoCommitReader;
autoCommitReader = ds.getFeatureReader(newFeatureQuery, Transaction.AUTO_COMMIT);
try {
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertFalse(autoCommitReader.hasNext());
} else {
assertTrue(autoCommitReader.hasNext());
}
} finally {
autoCommitReader.close();
}
}
// but yes inside it
{
FeatureReader<SimpleFeatureType, SimpleFeature> transactionReader;
transactionReader = ds.getFeatureReader(newFeatureQuery, transaction);
try {
assertFalse(transactionReader.hasNext());
} finally {
transactionReader.close();
}
}
{
FeatureReader<SimpleFeatureType, SimpleFeature> autoCommitReader;
try {
transaction.commit();
autoCommitReader = ds
.getFeatureReader(newFeatureQuery, Transaction.AUTO_COMMIT);
assertFalse(autoCommitReader.hasNext());
} catch (Exception e) {
transaction.rollback();
throw e;
}
}
} finally {
transaction.close();
}
}
@Test
public void testSetFeaturesAutoCommit() throws Exception {
testData.insertTestData();
final FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSet = testData
.createTestFeatures(Point.class, 5);
final DataStore ds = testData.getDataStore();
final String typeName = testData.getTempTableName();
final FeatureStore<SimpleFeatureType, SimpleFeature> store;
store = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds.getFeatureSource(typeName);
final int initialCount = store.getCount(Query.ALL);
assertTrue(initialCount > 0);
assertTrue(initialCount != 5);
store.setFeatures(DataUtilities.reader(featuresToSet));
final int newCount = store.getCount(Query.ALL);
assertEquals(5, newCount);
}
@Test
public void testSetFeaturesTransaction() throws Exception {
testData.insertTestData();
final FeatureCollection<SimpleFeatureType, SimpleFeature> featuresToSet = testData
.createTestFeatures(Point.class, 5);
final DataStore ds = testData.getDataStore();
final String typeName = testData.getTempTableName();
final Transaction transaction = new DefaultTransaction("testSetFeaturesTransaction handle");
final FeatureStore<SimpleFeatureType, SimpleFeature> store;
store = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds.getFeatureSource(typeName);
store.setTransaction(transaction);
final int initialCount = store.getCount(Query.ALL);
assertTrue(initialCount > 0);
assertTrue(initialCount != 5);
try {
store.setFeatures(DataUtilities.reader(featuresToSet));
final int countInsideTransaction = store.getCount(Query.ALL);
assertEquals(5, countInsideTransaction);
final FeatureSource<SimpleFeatureType, SimpleFeature> sourceNoTransaction;
sourceNoTransaction = ds.getFeatureSource(typeName);
int countNoTransaction = sourceNoTransaction.getCount(Query.ALL);
if (databaseIsMsSqlServer) {
// SQL Server always is at READ UNCOMMITTED isolation level...
assertEquals(countInsideTransaction, countNoTransaction);
} else {
assertEquals(initialCount, countNoTransaction);
}
// now commit
transaction.commit();
countNoTransaction = sourceNoTransaction.getCount(Query.ALL);
assertEquals(5, countNoTransaction);
} catch (Exception e) {
transaction.rollback();
throw e;
} catch (AssertionFailedError e) {
transaction.rollback();
throw e;
} finally {
transaction.close();
}
}
/**
* Simultate an application where one thread works over a transaction adding features while
* another thread accesses the same FeatureStore with a query. Archetypical use case being a
* udig addFeatures command sends calls addFeatures and the rendering thread does getFeatures.
*/
@Test
public void testTransactionMultithreadAccess() throws Exception {
testData.insertTestData();
// start with an empty table
final String typeName = testData.getTempTableName();
final int featureCount = 2;
final FeatureCollection<SimpleFeatureType, SimpleFeature> testFeatures = testData
.createTestFeatures(LineString.class, featureCount);
final DataStore ds = testData.getDataStore();
final FeatureStore<SimpleFeatureType, SimpleFeature> fStore = (FeatureStore<SimpleFeatureType, SimpleFeature>) ds
.getFeatureSource(typeName);
final Transaction transaction = new DefaultTransaction("testTransactionMultithreadAccess");
fStore.setTransaction(transaction);
final boolean[] done = { false, false };
final Throwable[] errors = new Throwable[2];
Runnable worker1 = new Runnable() {
public void run() {
try {
System.err.println("adding..");
List<FeatureId> addedFids = fStore.addFeatures(testFeatures);
System.err.println("got " + addedFids);
final FilterFactory ff = CommonFactoryFinder.getFilterFactory(null);
final Set<FeatureId> fids = new HashSet<FeatureId>();
for (FeatureId fid : addedFids) {
fids.add(fid);
}
final Id newFidsFilter = ff.id(fids);
System.err.println("querying..");
FeatureCollection<SimpleFeatureType, SimpleFeature> features = fStore
.getFeatures(newFidsFilter);
System.err.println("querying returned...");
int size = features.size();
System.err.println("Collection Size: " + size);
assertEquals(2, size);
System.err.println("commiting...");
transaction.commit();
System.err.println("commited.");
size = fStore.getCount(new DefaultQuery(typeName, newFidsFilter));
System.err.println("Size: " + size);
assertEquals(2, size);
} catch (Throwable e) {
errors[0] = e;
try {
System.err.println("rolling back!.");
transaction.rollback();
} catch (IOException e1) {
e1.printStackTrace();
}
} finally {
done[0] = true;
}
}
};
Runnable worker2 = new Runnable() {
public void run() {
try {
System.err.println("worker2 calling getFeartures()");
FeatureCollection<SimpleFeatureType, SimpleFeature> collection = fStore
.getFeatures();
System.err.println("worker2 opening iterator...");
FeatureIterator<SimpleFeature> features = collection.features();
try {
System.err.println("worker2 iterating...");
while (features.hasNext()) {
SimpleFeature next = features.next();
System.out.println("**Got feature " + next.getID());
}
System.err.println("worker2 closing FeatureCollection");
} finally {
features.close();
}
System.err.println("worker2 done.");
} catch (Throwable e) {
errors[1] = e;
} finally {
done[1] = true;
}
}
};
Thread thread1 = new Thread(worker1, "worker1");
Thread thread2 = new Thread(worker2, "worker2");
thread1.start();
thread2.start();
while (!(done[0] && done[1])) {
Thread.sleep(100);
}
try {
System.err.println("closing transaction.");
transaction.close();
} catch (IOException e) {
e.printStackTrace();
}
Throwable worker1Error = errors[0];
Throwable worker2Error = errors[1];
if (worker1Error != null || worker2Error != null) {
String errMessg = "worker1: "
+ (worker1Error == null ? "ok." : worker1Error.getMessage());
errMessg += " -- worker2: "
+ (worker2Error == null ? "ok." : worker2Error.getMessage());
if (worker1Error != null) {
worker1Error.printStackTrace();
}
if (worker2Error != null) {
worker2Error.printStackTrace();
}
fail(errMessg);
}
}
static class Watcher implements FeatureListener {
private Type type;
private Envelope bounds;
private FeatureSource<? extends FeatureType, ? extends Feature> source;
public void changed(FeatureEvent featureEvent) {
type = featureEvent.getType();
bounds = featureEvent.getBounds();
source = featureEvent.getFeatureSource();
}
}
public void testEditVersionedTableTransactionConcurrently() throws Exception {
try {
final String tableName;
{
ISession session = testData.getConnectionPool().getSession();
try {
SeTable versionedTable = testData.createVersionedTable(session);
tableName = versionedTable.getQualifiedName();
} finally {
session.dispose();
}
}
final ArcSDEDataStore dataStore = testData.getDataStore();
final FeatureSource<SimpleFeatureType, SimpleFeature> source;
final FeatureStore<SimpleFeatureType, SimpleFeature> store;
source = dataStore.getFeatureSource(tableName);
store = (FeatureStore<SimpleFeatureType, SimpleFeature>) dataStore
.getFeatureSource(tableName);
Transaction transaction = new DefaultTransaction();
store.setTransaction(transaction);
ArcSdeResourceInfo info = (ArcSdeResourceInfo) store.getInfo();
assertTrue(info.isVersioned());
final SimpleFeatureType schema = store.getSchema();
final int initialCount = store.getCount(Query.ALL);
assertEquals(0, initialCount);
final WKTReader reader = new WKTReader();
Object[] content = new Object[2];
SimpleFeature feature;
FeatureCollection<SimpleFeatureType, SimpleFeature> collection;
int count;
content[0] = "Feature name 1";
content[1] = reader.read("POINT (10 10)");
feature = SimpleFeatureBuilder.build(schema, content, (String) null);
collection = DataUtilities.collection(feature);
store.addFeatures(collection);
count = store.getCount(Query.ALL);
assertEquals(1, count);
assertEquals(0, source.getCount(Query.ALL));
{
FeatureIterator<SimpleFeature> features = store.getFeatures().features();
SimpleFeature f = features.next();
features.close();
Object obj = f.getDefaultGeometry();
assertTrue(obj instanceof Point);
Point p = (Point) obj;
double x = p.getX();
double y = p.getY();
assertEquals(10D, x, 1E-5);
assertEquals(10D, y, 1E-5);
}
transaction.commit();
assertEquals(1, source.getCount(Query.ALL));
content[0] = "Feature name 2";
content[1] = reader.read("POINT (2 2)");
feature = SimpleFeatureBuilder.build(schema, content, (String) null);
collection = DataUtilities.collection(feature);
store.addFeatures(collection);
count = store.getCount(Query.ALL);
assertEquals(2, count);
assertEquals(1, source.getCount(Query.ALL));
transaction.rollback();
assertEquals(1, store.getCount(Query.ALL));
transaction.close();
{
FeatureIterator<SimpleFeature> features = source.getFeatures().features();
SimpleFeature f = features.next();
features.close();
Object obj = f.getDefaultGeometry();
assertTrue(obj instanceof Point);
Point p = (Point) obj;
double x = p.getX();
double y = p.getY();
assertEquals(10D, x, 1E-5);
assertEquals(10D, y, 1E-5);
}
} catch (SeException e) {
throw new ArcSdeException(e);
}
}
}