Package org.geotools.arcsde.data

Source Code of org.geotools.arcsde.data.ArcSDEFeatureStoreTest

/*
*    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.DefaultTransaction;
import org.geotools.data.FeatureEvent;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureWriter;
import org.geotools.data.Query;
import org.geotools.data.Transaction;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.data.simple.SimpleFeatureIterator;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.data.simple.SimpleFeatureStore;
import org.geotools.factory.CommonFactoryFinder;
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.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.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 SimpleFeatureSource 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>();
        SimpleFeatureCollection 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");
        SimpleFeatureStore featureStore1 = (SimpleFeatureStore) 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");
        SimpleFeatureStore featureStore2 = (SimpleFeatureStore) 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));

        Query queryOneFeature = new Query();
        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 SimpleFeatureSource 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");
        SimpleFeatureStore featureStore1 = (SimpleFeatureStore) 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);
        SimpleFeatureCollection 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();
        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
            Query query = new Query(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?
        {
            Query query = new Query(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 Query(
                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 SimpleFeatureCollection testFeatures;
        testFeatures = testData.createTestFeatures(LineString.class, featureCount);

        final DataStore ds = testData.getDataStore();

        final SimpleFeatureType ftype = testFeatures.getSchema();
        final SimpleFeatureIterator 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 Query(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 SimpleFeatureCollection testFeatures;
        testFeatures = testData.createTestFeatures(LineString.class, featureCount);

        final DataStore ds = testData.getDataStore();
        final SimpleFeatureStore fStore;
        fStore = (SimpleFeatureStore) 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);

            SimpleFeatureCollection 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();

        SimpleFeatureStore store = (SimpleFeatureStore) ds.getFeatureSource(typeName);

        SimpleFeatureType ftype = store.getSchema();

        assertFalse(ftype.getDescriptor("INT32_COL").isNillable());

        feature.setAttribute("INT32_COL", null);

        SimpleFeatureCollection 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();
        }

        Query query = new Query(typeName, filter);
        FeatureReader<SimpleFeatureType, SimpleFeature> reader;
        reader = ds.getFeatureReader(query, Transaction.AUTO_COMMIT);
        try {
            assertFalse(reader.hasNext());
        } finally {
            reader.close();
        }

        query = new Query(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 Query(typeName, oldValueFilter);
        final Filter newValueFilter = CQL.toFilter("INT32_COL = -1000");
        final Query newValueQuery = new Query(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
    @SuppressWarnings("unchecked")
    public void testModifyFeaturesTransaction() throws Exception {
        testData.insertTestData();

        final String typeName = testData.getTempTableName();
        final DataStore ds = testData.getDataStore();
        final SimpleFeatureStore store;
        store = (SimpleFeatureStore) ds.getFeatureSource(typeName);
        final SimpleFeatureType schema = store.getSchema();
        final Filter oldValueFilter = CQL.toFilter("INT32_COL = 3");
        final Filter newValueFilter = CQL.toFilter("INT32_COL = -1000");

        SimpleFeatureCollection features = store.getFeatures(oldValueFilter);
        final int initialSize = features.size();
        assertEquals(1, initialSize);// just to not go forward with bad data
        final SimpleFeature originalFeature;
        SimpleFeatureIterator 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.getName(), Integer.valueOf(-1000),
                        oldValueFilter);
                transaction.commit();
            } catch (Exception e) {
                transaction.rollback();
            } finally {
                transaction.close();
            }
        }
        store.setTransaction(Transaction.AUTO_COMMIT);

        final Query oldValueQuery = new Query(typeName, oldValueFilter);
        final Query newValueQuery = new Query(typeName, newValueFilter);

        assertEquals(0, store.getCount(oldValueQuery));
        assertEquals(1, store.getCount(newValueQuery));

        final SimpleFeatureIterator 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 SimpleFeatureStore store;
        store = (SimpleFeatureStore) 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.getName(), modif2, fid2Filter);
            store.modifyFeatures(defaultGeometry.getName(), modif1, fid1Filter);
            transaction.commit();
        } catch (Exception e) {
            transaction.rollback();
            throw e;
        } finally {
            transaction.close();
        }
        store.setTransaction(Transaction.AUTO_COMMIT);

        try {
            SimpleFeatureCollection features;
            SimpleFeatureIterator 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.
     *
     */
    private void testInsertAutoCommit(Class<? extends Geometry> geometryClass) throws Exception {
        final String typeName = testData.getTempTableName();
        final int insertCount = 2;
        final SimpleFeatureCollection testFeatures;
        testFeatures = testData.createTestFeatures(geometryClass, insertCount);

        final DataStore ds = testData.getDataStore();
        final SimpleFeatureSource 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 (SimpleFeatureIterator 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 Query(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.");

        Query query = new Query(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
     *
     */
    @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 SimpleFeatureSource fsource = ds.getFeatureSource(typeName);

        final int initialCount = fsource.getCount(Query.ALL);
        final int writeCount = initialCount + 2;
        final SimpleFeatureCollection 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 (SimpleFeatureIterator 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]);
         */
    }

    @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 SimpleFeatureCollection testFeatures = testData.createTestFeatures(LineString.class,
                2);

        final DataStore ds = testData.getDataStore();
        final SimpleFeatureSource 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 (SimpleFeatureIterator 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 SimpleFeatureStore transFs;
        transFs = (SimpleFeatureStore) 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 Query newFeatureQuery = new Query(typeName, filterNewFeature);

            SimpleFeatureCollection 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 SimpleFeatureCollection featuresToSet = testData.createTestFeatures(Point.class, 5);
        final DataStore ds = testData.getDataStore();
        final String typeName = testData.getTempTableName();

        final SimpleFeatureStore store;
        store = (SimpleFeatureStore) 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 SimpleFeatureCollection featuresToSet = testData.createTestFeatures(Point.class, 5);
        final DataStore ds = testData.getDataStore();
        final String typeName = testData.getTempTableName();

        final Transaction transaction = new DefaultTransaction("testSetFeaturesTransaction handle");
        final SimpleFeatureStore store;
        store = (SimpleFeatureStore) 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 SimpleFeatureSource 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 SimpleFeatureCollection testFeatures = testData.createTestFeatures(LineString.class,
                featureCount);

        final DataStore ds = testData.getDataStore();
        final SimpleFeatureStore fStore = (SimpleFeatureStore) 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..");
                    SimpleFeatureCollection 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 Query(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()");
                    SimpleFeatureCollection collection = fStore.getFeatures();
                    System.err.println("worker2 opening iterator...");
                    SimpleFeatureIterator 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);
        }
    }

    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 SimpleFeatureSource source;
            final SimpleFeatureStore store;

            source = dataStore.getFeatureSource(tableName);
            store = (SimpleFeatureStore) 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;
            SimpleFeatureCollection 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));

            {
                SimpleFeatureIterator 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();

            {
                SimpleFeatureIterator 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);
        }
    }

}
TOP

Related Classes of org.geotools.arcsde.data.ArcSDEFeatureStoreTest

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.