/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2013, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.db.postgres; import com.vividsolutions.jts.geom.Coordinate; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.geom.GeometryCollection; 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 java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.sql.Connection; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Properties; import org.apache.sis.feature.FeatureExt; import org.apache.sis.feature.builder.FeatureTypeBuilder; import org.geotoolkit.data.FeatureCollection; import org.geotoolkit.data.FeatureIterator; import org.geotoolkit.data.FeatureReader; import org.geotoolkit.data.query.Query; import org.geotoolkit.data.query.QueryBuilder; import org.geotoolkit.data.session.Session; import org.geotoolkit.db.JDBCFeatureStore; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.geometry.jts.JTS; import org.geotoolkit.utility.parameter.ParametersExt; import org.geotoolkit.version.VersionControl; import org.geotoolkit.version.VersioningException; import org.junit.Assume; import org.junit.BeforeClass; import org.junit.Test; import org.opengis.util.GenericName; import org.opengis.parameter.ParameterValueGroup; import static org.geotoolkit.db.postgres.PostgresFeatureStoreFactory.*; import org.geotoolkit.filter.identity.DefaultFeatureId; import static org.junit.Assert.*; import org.geotoolkit.storage.DataStores; import org.junit.After; import org.opengis.filter.identity.FeatureId; import org.apache.sis.referencing.CommonCRS; import org.junit.Ignore; import org.opengis.feature.AttributeType; import org.opengis.feature.Feature; import org.opengis.feature.FeatureAssociationRole; import org.opengis.feature.FeatureType; import org.opengis.feature.Operation; import org.opengis.feature.PropertyType; /** * * @author Johann Sorel (Geomatys) */ @Ignore public class PostgresComplexTypeTest extends org.geotoolkit.test.TestBase { /** driver types */ private static final FeatureType FTYPE_DRIVER; /** stop types */ private static final FeatureType FTYPE_STOP; /** data types */ private static final FeatureType FTYPE_DATA; /** record types */ private static final FeatureType FTYPE_RECORD; /** sdata types */ private static final FeatureType FTYPE_SDATA; /** basic field types */ private static final FeatureType FTYPE_SIMPLE; /** 1 dimension arrays */ private static final FeatureType FTYPE_ARRAY; /** 2 dimensions arrays */ private static final FeatureType FTYPE_ARRAY2; /** geometric fields */ private static final FeatureType FTYPE_GEOMETRY; /** 1 depth feature type */ private static final FeatureType FTYPE_COMPLEX; /** 2 depth feature type */ private static final FeatureType FTYPE_COMPLEX2; /** multiple properties of same complex type */ private static final FeatureType FTYPE_COMPLEX3; static{ //////////////////////////////////////////////////////////////////////// FeatureTypeBuilder ftb = new FeatureTypeBuilder(); ftb.setName("testTable"); ftb.addAttribute(Boolean.class).setName("boolean"); ftb.addAttribute(Byte.class).setName("byte"); ftb.addAttribute(Short.class).setName("short"); ftb.addAttribute(Integer.class).setName("integer"); ftb.addAttribute(Long.class).setName("long"); ftb.addAttribute(Float.class).setName("float"); ftb.addAttribute(Double.class).setName("double"); ftb.addAttribute(String.class).setName("string"); FTYPE_SIMPLE = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("testTable"); ftb.addAttribute(Boolean[].class).setName("boolean"); ftb.addAttribute(Byte[].class).setName("byte"); ftb.addAttribute(Short[].class).setName("short"); ftb.addAttribute(Integer[].class).setName("integer"); ftb.addAttribute(Long[].class).setName("long"); ftb.addAttribute(Float[].class).setName("float"); ftb.addAttribute(Double[].class).setName("double"); ftb.addAttribute(String[].class).setName("string"); FTYPE_ARRAY = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("testTable"); ftb.addAttribute(Boolean[][].class).setName("boolean"); ftb.addAttribute(Byte[][].class).setName("byte"); ftb.addAttribute(Short[][].class).setName("short"); ftb.addAttribute(Integer[][].class).setName("integer"); ftb.addAttribute(Long[][].class).setName("long"); ftb.addAttribute(Float[][].class).setName("float"); ftb.addAttribute(Double[][].class).setName("double"); ftb.addAttribute(String[][].class).setName("string"); FTYPE_ARRAY2 = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("testTable"); ftb.addAttribute(Geometry.class).setName("geometry").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(Point.class).setName("point").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(MultiPoint.class).setName("multipoint").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(LineString.class).setName("linestring").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(MultiLineString.class).setName("multilinestring").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(Polygon.class).setName("polygon").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(MultiPolygon.class).setName("multipolygon").setCRS(CommonCRS.WGS84.normalizedGeographic()); ftb.addAttribute(GeometryCollection.class).setName("geometrycollection").setCRS(CommonCRS.WGS84.normalizedGeographic()); FTYPE_GEOMETRY = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("Stop"); ftb.addAttribute(Point.class).setName("location").setCRS(CommonCRS.defaultGeographic()); ftb.addAttribute(Date.class).setName("time"); FTYPE_STOP = ftb.build(); ftb = new FeatureTypeBuilder(); ftb.setName("Driver"); ftb.addAttribute(String.class).setName("name"); ftb.addAttribute(String.class).setName("code"); FTYPE_DRIVER = ftb.build(); ftb = new FeatureTypeBuilder(); ftb.setName("Voyage"); ftb.addAttribute(Long.class).setName("identifier"); ftb.addAssociation(FTYPE_DRIVER).setName("driver"); ftb.addAssociation(FTYPE_STOP).setName("stops").setMinimumOccurs(0).setMaximumOccurs(Integer.MAX_VALUE); FTYPE_COMPLEX = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("Data"); ftb.addAttribute(Float[].class).setName("values"); FTYPE_DATA = ftb.build(); ftb = new FeatureTypeBuilder(); ftb.setName("Record"); ftb.addAttribute(Date.class).setName("time"); ftb.addAssociation(FTYPE_DATA).setName("datas").setMinimumOccurs(0).setMaximumOccurs(Integer.MAX_VALUE); FTYPE_RECORD = ftb.build(); ftb = new FeatureTypeBuilder(); ftb.setName("Sounding"); ftb.addAttribute(Long.class).setName("identifier"); ftb.addAssociation(FTYPE_RECORD).setName("records").setMinimumOccurs(0).setMaximumOccurs(Integer.MAX_VALUE); FTYPE_COMPLEX2 = ftb.build(); //////////////////////////////////////////////////////////////////////// ftb = new FeatureTypeBuilder(); ftb.setName("Data"); ftb.addAttribute(Float.class).setName("value"); FTYPE_SDATA = ftb.build(); ftb = new FeatureTypeBuilder(); ftb.setName("Record"); ftb.addAttribute(Long.class).setName("identifier"); ftb.addAssociation(FTYPE_SDATA).setName("data1"); ftb.addAssociation(FTYPE_SDATA).setName("data2"); ftb.addAssociation(FTYPE_SDATA).setName("data3"); FTYPE_COMPLEX3 = ftb.build(); } private PostgresFeatureStore store; public PostgresComplexTypeTest(){ } private static ParameterValueGroup params; /** * <p>Find JDBC connection parameters in specified file at * "/home/.geotoolkit.org/test-pgfeature.properties".<br/> * If properties file doesn't find all tests are skipped.</p> * * <p>To lunch tests user should create file with this architecture<br/> * for example : <br/> * database = junit (table name)<br/> * port = 5432 (port number)<br/> * schema = public (schema name)<br/> * user = postgres (user login)<br/> * password = postgres (user password)<br/> * simpletype = false <br/> * namespace = no namespace</p> * @throws IOException */ @BeforeClass public static void beforeClass() throws IOException { String path = System.getProperty("user.home"); path += "/.geotoolkit.org/test-pgfeature.properties"; final File f = new File(path); Assume.assumeTrue(f.exists()); final Properties properties = new Properties(); properties.load(new FileInputStream(f)); params = FeatureExt.toParameter((Map)properties, PARAMETERS_DESCRIPTOR, false); } private void reload(boolean simpleType) throws DataStoreException, VersioningException { if(store != null){ store.close(); } //open in complex type to delete all types ParametersExt.getOrCreateValue(params, PostgresFeatureStoreFactory.SIMPLETYPE.getName().getCode()).setValue(false); store = (PostgresFeatureStore) DataStores.open(params); while(!store.getNames().isEmpty()){ // we get the list each type because relations may delete multiple types each time final GenericName n = store.getNames().iterator().next(); final VersionControl vc = store.getVersioning(n.toString()); vc.dropVersioning(); store.deleteFeatureType(n.toString()); } assertTrue(store.getNames().isEmpty()); store.close(); //reopen the way it was asked ParametersExt.getOrCreateValue(params, PostgresFeatureStoreFactory.SIMPLETYPE.getName().getCode()).setValue(simpleType); store = (PostgresFeatureStore) DataStores.open(params); assertTrue(store.getNames().isEmpty()); } @After public void disposeStore() { if (store != null) { store.close(); } } @Test public void testCrossSchemaRelation() throws DataStoreException, VersioningException, SQLException{ reload(false); try (Connection cnx = store.getDataSource().getConnection()) { cnx.createStatement().executeUpdate("CREATE TABLE \"localtable\" (id serial, other integer);"); cnx.createStatement().executeUpdate("DROP SCHEMA IF EXISTS someothertestschema CASCADE;"); cnx.createStatement().executeUpdate("CREATE SCHEMA someothertestschema;"); cnx.createStatement().executeUpdate("CREATE TABLE \"someothertestschema\".\"othertable\" (ident serial PRIMARY KEY, field double precision);"); cnx.createStatement().executeUpdate("ALTER TABLE \"localtable\" ADD FOREIGN KEY (other) REFERENCES someothertestschema.othertable(ident)"); } store.refreshMetaModel(); final FeatureType ft = store.getFeatureType("localtable"); assertEquals("localtable", ft.getName().tip().toString()); assertEquals(3,ft.getProperties(true).size()); assertNotNull(ft.getProperty("id")); assertEquals(Integer.class, ((AttributeType)ft.getProperty("id")).getValueClass()); final PropertyType desc = ft.getProperty("other"); assertNotNull(desc); assertTrue(desc instanceof Operation); FeatureAssociationRole far = (FeatureAssociationRole) ((Operation)desc).getResult(); final FeatureType ct = far.getValueType(); assertEquals(3,ct.getProperties(true).size()); assertNotNull(ct.getProperty("ident")); assertEquals(Integer.class, ((AttributeType)ct.getProperty("ident")).getValueClass()); assertNotNull(ct.getProperty("field")); assertEquals(Double.class, ((AttributeType)ct.getProperty("field")).getValueClass()); } @Test public void testFeatureTypeCreation() throws DataStoreException, VersioningException{ reload(false); final FeatureType refType = FTYPE_COMPLEX; store.createFeatureType(refType); assertEquals(1, store.getNames().size()); final GenericName name = store.getNames().iterator().next(); final FeatureType created = store.getFeatureType(name.toString()); lazyCompare(refType, created); } @Test public void testFeatureType2Creation() throws DataStoreException, VersioningException{ reload(false); final FeatureType refType = FTYPE_COMPLEX2; store.createFeatureType(refType); assertEquals(1, store.getNames().size()); final GenericName name = store.getNames().iterator().next(); final FeatureType created = store.getFeatureType(name.toString()); lazyCompare(refType, created); } @Test public void testFeatureType3Creation() throws DataStoreException, VersioningException{ reload(false); final FeatureType refType = FTYPE_COMPLEX3; store.createFeatureType(refType); assertEquals(1, store.getNames().size()); final FeatureType created = store.getFeatureType(FTYPE_COMPLEX3.getName().tip().toString()); lazyCompare(refType, created); } /** * 2 level depths feature test. */ @Test public void testComplexInsert() throws DataStoreException, VersioningException{ reload(false); final GeometryFactory gf = new GeometryFactory(); store.createFeatureType(FTYPE_COMPLEX); final FeatureType resType = store.getFeatureType(store.getNames().iterator().next().toString()); final Feature voyage = resType.newInstance(); voyage.setPropertyValue("identifier",120l); final Feature driver = FTYPE_DRIVER.newInstance(); driver.setPropertyValue("name","jean-michel"); driver.setPropertyValue("code","BHF:123456"); voyage.setPropertyValue("driver", driver); final Feature stop1 = FTYPE_STOP.newInstance(); stop1.setPropertyValue("location",gf.createPoint(new Coordinate(-10, 60))); stop1.setPropertyValue("time",new Date(5000000)); final Feature stop2 = FTYPE_STOP.newInstance(); stop2.setPropertyValue("location",gf.createPoint(new Coordinate(30, 15))); stop2.setPropertyValue("time",new Date(6000000)); final Feature stop3 = FTYPE_STOP.newInstance(); stop3.setPropertyValue("location",gf.createPoint(new Coordinate(40, -70))); stop3.setPropertyValue("time",new Date(7000000)); voyage.setPropertyValue("stops", Arrays.asList(stop1,stop2,stop3)); List<FeatureId> addedIds = store.addFeatures(resType.getName().toString(), Collections.singleton(voyage)); assertEquals(1, addedIds.size()); assertEquals(new DefaultFeatureId("Voyage.1"), addedIds.get(0)); final Session session = store.createSession(false); final FeatureCollection col = session.getFeatureCollection(QueryBuilder.all(resType.getName().toString())); assertEquals(1, col.size()); final FeatureIterator ite = col.iterator(); try{ final Feature resFeature = ite.next(); assertNotNull(resFeature); assertEquals(120l, resFeature.getPropertyValue("identifier")); final Feature resDriver = (Feature) resFeature.getProperty("driver"); assertEquals("jean-michel", resDriver.getPropertyValue("name")); assertEquals("BHF:123456", resDriver.getPropertyValue("code")); final Collection<Feature> stops = (Collection<Feature>) resFeature.getPropertyValue("stops"); assertEquals(3, stops.size()); final boolean[] found = new boolean[3]; for(Feature stop : stops){ final Timestamp time = (Timestamp) stop.getPropertyValue("time"); final Point location = (Point) stop.getPropertyValue("location"); if(time.getTime() == 5000000){ assertEquals(stop1.getPropertyValue("location"), location); found[0] = true; }else if(time.getTime() == 6000000){ assertEquals(stop2.getPropertyValue("location"), location); found[1] = true; }else if(time.getTime() == 7000000){ assertEquals(stop3.getPropertyValue("location"), location); found[2] = true; }else{ fail("Unexpected property \n"+stop); } } for(boolean b : found) assertTrue(b); }finally{ ite.close(); } } /** * 3 level depths feature test. */ @Test public void testComplex2Insert() throws DataStoreException, VersioningException{ reload(false); store.createFeatureType(FTYPE_COMPLEX2); final FeatureType soundingType = store.getFeatureType(store.getNames().iterator().next().toString()); final FeatureType recordType = ((FeatureAssociationRole)soundingType.getProperty("records")).getValueType(); final FeatureType dataType = ((FeatureAssociationRole)recordType.getProperty("datas")).getValueType(); final Feature sounding = soundingType.newInstance(); sounding.setPropertyValue("identifier",120l); final Feature record1 = recordType.newInstance(); record1.setPropertyValue("time",new Date(5000000)); final Feature data11 = dataType.newInstance(); data11.setPropertyValue("values",new Float[]{1f,2f,3f}); final Feature data12 = dataType.newInstance(); data12.setPropertyValue("values",new Float[]{4f,5f,6f}); record1.setPropertyValue("datas", Arrays.asList(data11,data12)); final Feature record2 = recordType.newInstance(); record2.setPropertyValue("time",new Date(6000000)); final Feature data21 = dataType.newInstance(); data21.setPropertyValue("values",new Float[]{7f,8f,9f}); record2.setPropertyValue("datas", Arrays.asList(data21)); sounding.setPropertyValue("records", Arrays.asList(record1,record2)); List<FeatureId> addedIds = store.addFeatures(soundingType.getName().toString(), Collections.singleton(sounding)); assertEquals(1, addedIds.size()); assertEquals(new DefaultFeatureId("Sounding.1"), addedIds.get(0)); final Session session = store.createSession(false); final FeatureCollection col = session.getFeatureCollection(QueryBuilder.all(soundingType.getName().toString())); assertEquals(1, col.size()); final FeatureIterator ite = store.getFeatureReader(QueryBuilder.all(soundingType.getName().toString())); try{ final Feature resFeature = ite.next(); assertNotNull(resFeature); assertEquals(120l, resFeature.getPropertyValue("identifier")); final Collection<Feature> records = (Collection<Feature>) resFeature.getPropertyValue("records"); assertEquals(2, records.size()); final boolean[] found = new boolean[2]; for(Feature record : records){ final Timestamp time = (Timestamp) record.getPropertyValue("time"); if(time.getTime() == 5000000){ found[0] = true; final Collection<Feature> datas = (Collection<Feature>) record.getPropertyValue("datas"); assertEquals(2, datas.size()); final boolean[] dfound = new boolean[2]; for(Feature data : datas){ final Float[] values = (Float[]) data.getPropertyValue("values"); if(Arrays.equals(values, new Float[]{1f,2f,3f})){ dfound[0] = true; }else if(Arrays.equals(values, new Float[]{4f,5f,6f})){ dfound[1] = true; }else{ fail("Unexpected property \n"+data); } } for(boolean b : dfound) assertTrue(b); }else if(time.getTime() == 6000000){ found[1] = true; final Collection<Feature> datas = (Collection<Feature>) record.getPropertyValue("datas"); assertEquals(1, datas.size()); final boolean[] dfound = new boolean[1]; for(Feature data : datas){ final Float[] values = (Float[]) data.getPropertyValue("values"); if(Arrays.equals(values, new Float[]{7f,8f,9f})){ dfound[0] = true; }else{ fail("Unexpected property \n"+data); } } for(boolean b : dfound) assertTrue(b); }else{ fail("Unexpected property \n"+record); } } for(boolean b : found) assertTrue(b); }finally{ ite.close(); } } /** * multiple complex properties of same type * @throws org.apache.sis.storage.DataStoreException * @throws org.geotoolkit.version.VersioningException */ @Test public void testComplex3Insert() throws DataStoreException, VersioningException{ reload(false); store.createFeatureType(FTYPE_COMPLEX3); final FeatureType recordType = store.getFeatureType(store.getNames().iterator().next().toString()); final Feature record = FTYPE_RECORD.newInstance(); record.setPropertyValue("identifier",120); final Feature data1 = FTYPE_DATA.newInstance(); data1.setPropertyValue("value",5f); final Feature data2 = FTYPE_DATA.newInstance(); data2.setPropertyValue("value",10f); final Feature data3 = FTYPE_DATA.newInstance(); data3.setPropertyValue("value",15f); record.setPropertyValue("datas", Arrays.asList(data1,data2,data3)); List<FeatureId> addedIds = store.addFeatures(recordType.getName().toString(), Collections.singleton(record)); assertEquals(1, addedIds.size()); assertEquals(new DefaultFeatureId("Record.1"), addedIds.get(0)); final Session session = store.createSession(false); final FeatureCollection col = session.getFeatureCollection(QueryBuilder.all(recordType.getName().toString())); assertEquals(1, col.size()); final FeatureIterator ite = store.getFeatureReader(QueryBuilder.all(recordType.getName().toString())); try{ final Feature resFeature = ite.next(); assertNotNull(resFeature); assertEquals(120l, resFeature.getPropertyValue("identifier")); assertNotNull(resFeature.getProperty("data1")); assertNotNull(resFeature.getProperty("data2")); assertNotNull(resFeature.getProperty("data3")); assertEquals(5f, ((Feature)resFeature.getPropertyValue("data1")).getPropertyValue("value")); assertEquals(10f, ((Feature)resFeature.getPropertyValue("data2")).getPropertyValue("value")); assertEquals(15f, ((Feature)resFeature.getPropertyValue("data3")).getPropertyValue("value")); }finally{ ite.close(); } } /** * Test hand made query. * * @throws DataStoreException * @throws VersioningException */ @Test public void testHandMadeSQLQuery() throws Exception{ reload(false); final GeometryFactory gf = new GeometryFactory(); store.createFeatureType(FTYPE_COMPLEX); final FeatureType resType = store.getFeatureType(store.getNames().iterator().next().toString()); final Feature voyage = resType.newInstance(); voyage.setPropertyValue("identifier",120l); final Feature driver = FTYPE_DRIVER.newInstance(); driver.setPropertyValue("name","jean-michel"); driver.setPropertyValue("code","BHF:123456"); voyage.setPropertyValue("driver", driver); final Feature stop1 = FTYPE_STOP.newInstance(); stop1.setPropertyValue("location",gf.createPoint(new Coordinate(-10, 60))); stop1.setPropertyValue("time",new Date(5000000)); final Feature stop2 = FTYPE_STOP.newInstance(); stop2.setPropertyValue("location",gf.createPoint(new Coordinate(30, 15))); stop2.setPropertyValue("time",new Date(6000000)); final Feature stop3 = FTYPE_STOP.newInstance(); stop3.setPropertyValue("location",gf.createPoint(new Coordinate(40, -70))); stop3.setPropertyValue("time",new Date(7000000)); voyage.setPropertyValue("stops", Arrays.asList(stop1,stop2,stop3)); List<FeatureId> addedIds = store.addFeatures(resType.getName().toString(), Collections.singleton(voyage)); assertEquals(1, addedIds.size()); assertEquals(new DefaultFeatureId("Voyage.1"), addedIds.get(0)); final Query query = QueryBuilder.language(JDBCFeatureStore.CUSTOM_SQL, "SELECT * FROM \"Stop\"", "s1"); final FeatureReader ite = store.getFeatureReader(query); final boolean[] found = new boolean[3]; try{ while(ite.hasNext()){ final Feature feature = ite.next(); final Timestamp time = (Timestamp) feature.getPropertyValue("time"); final Point location = (Point) feature.getPropertyValue("location"); if(time.getTime() == 5000000){ assertEquals(stop1.getPropertyValue("location"), location); found[0] = true; }else if(time.getTime() == 6000000){ assertEquals(stop2.getPropertyValue("location"), location); found[1] = true; }else if(time.getTime() == 7000000){ assertEquals(stop3.getPropertyValue("location"), location); found[2] = true; }else{ fail("Unexpected property \n"+feature); } assertNotNull(JTS.findCoordinateReferenceSystem((Geometry)FeatureExt.getDefaultGeometryAttributeValue(feature))); } }finally{ ite.close(); } for(boolean b : found) assertTrue(b); } private void lazyCompare(final FeatureType refType, final FeatureType candidate){ final GenericName name = refType.getName(); assertEquals(refType.getName().tip().toString(), name.tip().toString()); if(refType instanceof FeatureType){ final FeatureType ct = (FeatureType) refType; final FeatureType cct = (FeatureType) candidate; assertEquals(ct.getProperties(true).size()+1, cct.getProperties(true).size());// +1 for generated fid field for(PropertyType desc : ct.getProperties(true)){ final PropertyType cdesc = cct.getProperty(desc.getName().toString()); assertEquals(desc, cdesc); } }else{ final AttributeType at = (AttributeType) refType; final AttributeType cat = (AttributeType) candidate; if(at.getValueClass()==Date.class){ assertEquals(Timestamp.class, cat.getValueClass()); }else{ assertEquals(at.getValueClass(), cat.getValueClass()); } } } }