/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2004-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.data.complex; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.io.Serializable; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.geotools.data.DataAccess; import org.geotools.data.DataAccessFinder; import org.geotools.data.FeatureSource; import org.geotools.data.complex.config.CatalogUtilities; import org.geotools.data.complex.config.EmfAppSchemaReader; import org.geotools.data.complex.config.FeatureTypeRegistry; import org.geotools.data.property.PropertyDataStore; import org.geotools.factory.Hints; import org.geotools.feature.AttributeImpl; import org.geotools.feature.ComplexAttributeImpl; import org.geotools.feature.FeatureCollection; import org.geotools.feature.FeatureImpl; import org.geotools.feature.NameImpl; import org.geotools.feature.Types; import org.geotools.feature.type.AttributeDescriptorImpl; import org.geotools.feature.type.FeatureTypeImpl; import org.geotools.filter.AttributeExpressionImpl; import org.geotools.filter.expression.FeaturePropertyAccessorFactory; import org.geotools.gml3.GMLSchema; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; import org.geotools.xml.SchemaIndex; import org.opengis.feature.Attribute; import org.opengis.feature.Feature; import org.opengis.feature.Property; import org.opengis.feature.simple.SimpleFeature; import org.opengis.feature.simple.SimpleFeatureType; import org.opengis.feature.type.AttributeDescriptor; import org.opengis.feature.type.AttributeType; import org.opengis.feature.type.ComplexType; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.Filter; import org.opengis.filter.expression.Expression; import org.opengis.filter.identity.FeatureId; /** * This is to test the integration of a data access (which does not necessarily have to be an * app-schema data access) that produces complex features of a certain XML form as an input to an * app-schema data access of a different XML form. A new app-schema data access would be created to * remap the non-app-schema data access into the output XML form. Then the features can chain or be * chained as per normal. See FeatureChainingTest.java to see feature chaining in action. * * @author Rini Angreani, Curtin University of Technology * * @source $URL$ */ public class AppSchemaDataAccessIntegrationTest extends DataAccessIntegrationTest { private static final String MO_URI = "urn:cgi:xmlns:GGIC:MineralOccurrence:1.0"; private static final Name EARTH_RESOURCE = Types.typeName(MO_URI, "EarthResource"); private static final Name EARTH_RESOURCE_TYPE = Types.typeName(MO_URI, "EarthResourceType"); private static final Name MINERAL_DEPOSIT_TYPE = Types.typeName(MO_URI, "MineralDepositModelType"); private static final Name MINERAL_DEPOSIT_PROPERTY_TYPE = Types.typeName(MO_URI, "MineralDepositModelPropertyType"); /** * Remapped Geologic Unit data access in GSML form */ private static DataAccess<FeatureType, Feature> newGuDataAccess; /** * Create the input data access containing complex features of MO form. */ @BeforeClass public static void setUp() throws Exception { Map<String, Serializable> moParams = new HashMap<String, Serializable>(); moParams.put("dbtype", "mo-data-access"); moParams.put("directory", AppSchemaDataAccessIntegrationTest.class.getResource(schemaBase)); // get original non-app-schema data access DataAccessFinder.getDataStore(moParams); DataAccessIntegrationTest.setFilterFactory(); // load app-schema data access instances loadGeologicUnitDataAccess(); loadDataAccess("MappedFeatureAsOccurrence.xml"); } /** * Dispose all the data accesses so that there is no mapping conflicts for other tests */ @AfterClass public static void tearDown() { DataAccessRegistry.unregisterAll(); } private static void loadGeologicUnitDataAccess() throws IOException { URL url = AppSchemaDataAccessIntegrationTest.class.getResource(schemaBase + "EarthResourceToGeologicUnit.xml"); assertNotNull(url); Map<String, Serializable> dsParams = new HashMap<String, Serializable>(); dsParams.put("dbtype", "app-schema"); dsParams.put("url", url.toExternalForm()); // create new app-schema data access using the existing non-app-schema inputDataAccess // and EarthResourceToGeologicUnit mapping file newGuDataAccess = DataAccessFinder.getDataStore(dsParams); assertNotNull(newGuDataAccess); guFeatureSource = newGuDataAccess.getFeatureSource(GEOLOGIC_UNIT); assertNotNull(guFeatureSource); } /** * Create complex features of type MO:EarthResource * * @param fCollection * Simple features collection * @param earthResourceType * Earth Resource schema * @return MO:EarthResource features * @throws IOException */ private static ArrayList<Feature> getInputFeatures( FeatureCollection<SimpleFeatureType, SimpleFeature> fCollection, ComplexType complexType) throws IOException { ArrayList<Feature> features = new ArrayList<Feature>(); FeatureType earthResourceType = new FeatureTypeImpl(complexType.getName(), complexType .getDescriptors(), null, true, complexType.getRestrictions(), GMLSchema.ABSTRACTFEATURETYPE_TYPE, null); AttributeDescriptor featureDesc = new AttributeDescriptorImpl(earthResourceType, EARTH_RESOURCE, 0, -1, false, null); ComplexType mineralDepositType = (ComplexType) typeRegistry .getAttributeType(MINERAL_DEPOSIT_TYPE); ComplexType mineralDepositPropertyType = (ComplexType) typeRegistry .getAttributeType(MINERAL_DEPOSIT_PROPERTY_TYPE); // for simple string properties Name name = new NameImpl(null, "simpleContent"); AttributeType simpleContentType = typeRegistry.getAttributeType(Types.typeName( "http://www.w3.org/2001/XMLSchema", "string")); AttributeDescriptor stringDescriptor = new AttributeDescriptorImpl(simpleContentType, name, 1, 1, true, (Object) null); Iterator<SimpleFeature> simpleFeatures = fCollection.iterator(); // gml:name descriptor AttributeDescriptor nameDescriptor = (AttributeDescriptor) GMLSchema.ABSTRACTGMLTYPE_TYPE .getDescriptor(Types.typeName(GMLNS, "name")); while (simpleFeatures.hasNext()) { SimpleFeature next = simpleFeatures.next(); Collection<Property> properties = new ArrayList<Property>(); ArrayList<Property> value = new ArrayList<Property>(); // mo:form String propertyName = "FORM"; value.add(new AttributeImpl(next.getProperty(propertyName).getValue(), stringDescriptor, null)); properties.add(new ComplexAttributeImpl(value, (AttributeDescriptor) earthResourceType .getDescriptor(Types.typeName(MO_URI, "form")), null)); // gml:name[1] value = new ArrayList<Property>(); value.add(new AttributeImpl("gu." + next.getID(), stringDescriptor, null)); ComplexAttributeImpl name1 = new ComplexAttributeImpl(value, nameDescriptor, null); properties.add(name1); // gml:name[2] value = new ArrayList<Property>(); value.add(new AttributeImpl("er." + next.getID(), stringDescriptor, null)); ComplexAttributeImpl name2 = new ComplexAttributeImpl(value, nameDescriptor, null); properties.add(name2); // mo:classification propertyName = "CLASSIFICATION"; ComplexAttributeImpl classification = new ComplexAttributeImpl( new ArrayList<Property>(), (AttributeDescriptor) earthResourceType .getDescriptor(Types.typeName(MO_URI, "classification")), null); // mo:classification/mo:MineralDepositModel/mo:mineralDepositGroup value = new ArrayList<Property>(); value.add(new AttributeImpl(next.getProperty(propertyName).getValue(), stringDescriptor, null)); Name leafAttribute = Types.typeName(MO_URI, "mineralDepositGroup"); AttributeImpl mineralDepositGroup = new AttributeImpl(value, (AttributeDescriptor) mineralDepositType.getDescriptor(leafAttribute), null); value = new ArrayList<Property>(); value.add(mineralDepositGroup); // mo:classification/mo:MineralDepositModel ComplexAttributeImpl mineralDepositModel = new ComplexAttributeImpl(value, (AttributeDescriptor) mineralDepositPropertyType.getDescriptor(Types.typeName( MO_URI, "MineralDepositModel")), null); value = new ArrayList<Property>(); value.add(mineralDepositModel); classification.setValue(value); properties.add(classification); // mo:composition propertyName = "COMPOSITION"; String[] cpIds = next.getProperty(propertyName).getValue().toString().split(","); for (String cpId : cpIds) { Collection<Property> cpProperties = new ArrayList<Property>(cpIds.length); cpProperties.add(new AttributeImpl(cpId, stringDescriptor, null)); properties.add(new AttributeImpl(cpProperties, (AttributeDescriptor) earthResourceType.getDescriptor(Types.typeName( MO_URI, "composition")), null)); } // mo:commodityDescription propertyName = "COMMODITYDESCRIPTION"; String[] mfIds = next.getProperty(propertyName).getValue().toString().split(","); for (String mfId : mfIds) { ArrayList<Property> mfProperties = new ArrayList<Property>(); mfProperties.add(new AttributeImpl(mfId, stringDescriptor, null)); properties.add(new AttributeImpl(mfProperties, (AttributeDescriptor) earthResourceType.getDescriptor(Types.typeName( MO_URI, "commodityDescription")), null)); } features.add(new FeatureImpl(properties, featureDesc, next.getIdentifier())); } fCollection.close(simpleFeatures); return features; } /** * Test that the app-schema data access with MO data access input loads successfully. * * @throws IOException * @throws URISyntaxException */ @Test public void testLoadDataAccess() throws IOException, URISyntaxException { // get the re-mapped geologic unit features FeatureCollection<FeatureType, Feature> guFeatures = guFeatureSource.getFeatures(); Iterator<Feature> guIterator = guFeatures.iterator(); ArrayList<String> guIds = new ArrayList<String>(); while (guIterator.hasNext()) { guIds.add(guIterator.next().getIdentifier().toString()); } guFeatures.close(guIterator); // get the simple earth resource features File dir = new File(getClass().getResource(schemaBase).toURI()); PropertyDataStore dataStore = new PropertyDataStore(dir); FeatureSource<SimpleFeatureType, SimpleFeature> simpleFeatureSource = dataStore .getFeatureSource(EARTH_RESOURCE); FeatureCollection<SimpleFeatureType, SimpleFeature> moFeatures = simpleFeatureSource .getFeatures(); Iterator<SimpleFeature> moIterator = moFeatures.iterator(); ArrayList<String> moIds = new ArrayList<String>(); while (moIterator.hasNext()) { moIds.add(moIterator.next().getIdentifier().toString()); } moFeatures.close(moIterator); // compare the feature ids and make sure that the features are all there assertEquals(guIds.size(), moIds.size()); assertTrue(guIds.containsAll(moIds)); } /** * Test that the re-mapping from MO:EarthResource to GSML:GeologicUnit is successful. This also * tests feature chaining for the mapped GU features. * * @throws IOException */ @Override @Test public void testMappings() throws IOException { FeatureCollection<FeatureType, Feature> guCollection = (FeatureCollection<FeatureType, Feature>) guFeatureSource .getFeatures(); // mo:EarthResource -> gsml:GeologicUnit output iterator AbstractMappingFeatureIterator iterator = (AbstractMappingFeatureIterator) guCollection.iterator(); FeatureTypeMapping guSchema = AppSchemaDataAccessRegistry.getMappingByElement(GEOLOGIC_UNIT); Hints hints = new Hints(FeaturePropertyAccessorFactory.NAMESPACE_CONTEXT, guSchema .getNamespaces()); // find attribute mappings for chained features final String composition = "composition"; final String occurrence = "occurence"; final String commodity = "commodityDescription"; List<AttributeMapping> otherMappings = new ArrayList<AttributeMapping>(); AttributeMapping compositionMapping = null; AttributeMapping occurrenceMapping = null; for (AttributeMapping attMapping : guSchema.getAttributeMappings()) { String attName = attMapping.getTargetXPath().toString(); if (attName.equals("gsml:" + composition)) { compositionMapping = attMapping; } else if (attName.equals("gsml:" + occurrence)) { occurrenceMapping = attMapping; } else { // normal inline attribute mappings (not chained) otherMappings.add(attMapping); } } // make sure all the mappings are there assertNotNull(occurrenceMapping); assertNotNull(compositionMapping); assertEquals(guSchema.getAttributeMappings().size() - 2, otherMappings.size()); int guCount = 0; ArrayList<Feature> guFeatures = new ArrayList<Feature>(); while (iterator.hasNext()) { Feature next = (Feature) iterator.next(); FeatureId fId = next.getIdentifier(); Feature moFeature = null; // find matching input MO feature to compare the values with for (Feature inputFeature : inputFeatures) { if (iterator.featureFidMapping.evaluate(inputFeature).equals(fId.toString())) { moFeature = inputFeature; } } assertNotNull(moFeature); /** * Check Feature Chaining : Composition Part as composition */ Collection<Property> gsmlCompositions = (Collection<Property>) next .getProperties(composition); Collection<Property> moCompositions = (Collection<Property>) moFeature .getProperties(composition); Collection<String> cpIds = new ArrayList<String>(); for (Property inputProperty : moCompositions) { Collection<Attribute> values = (Collection<Attribute>) inputProperty.getValue(); for (Attribute attrib : values) { cpIds.add(attrib.getValue().toString()); } } assertTrue(cpIds.size() > 0); assertEquals(gsmlCompositions.size(), cpIds.size()); ArrayList<String> nestedCpIds = new ArrayList<String>(cpIds.size()); for (Property outputProperty : gsmlCompositions) { Collection<Feature> values = (Collection<Feature>) outputProperty.getValue(); Feature compositionPart = values.iterator().next(); // check the values assertTrue(cpFeatures.contains(compositionPart)); nestedCpIds.add(compositionPart.getIdentifier().toString()); } // check the feature has the correct id assertTrue(cpIds.containsAll(nestedCpIds)); /** * Check Feature Chaining : Mapped Feature as occurrence */ Collection<Property> occurrences = (Collection<Property>) next .getProperties(occurrence); Collection<Property> commodities = (Collection<Property>) moFeature .getProperties(commodity); Collection<String> mfIds = new ArrayList<String>(); for (Property property : commodities) { Collection<Attribute> values = (Collection<Attribute>) property.getValue(); for (Attribute attrib : values) { mfIds.add(attrib.getValue().toString()); } } assertTrue(mfIds.size() > 0); assertEquals(occurrences.size(), mfIds.size()); ArrayList<String> nestedMfIds = new ArrayList<String>(mfIds.size()); for (Property mf : occurrences) { Collection<Feature> values = (Collection<Feature>) mf.getValue(); Feature mfFeature = values.iterator().next(); // check the values assertTrue(mfFeatures.contains(mfFeature)); nestedMfIds.add(mfFeature.getIdentifier().toString()); } // check the feature has the correct id assertTrue(mfIds.containsAll(nestedMfIds)); // check multi-valued properties are all mapped // there should be 2 gml:name attributes, although only mapped once // <AttributeMapping> // <!-- All instances of gml:name should be mapped, how many is not known --> // <targetAttribute>gml:name</targetAttribute> // <sourceExpression> // <inputAttribute>gml:name</inputAttribute> // </sourceExpression> // <isMultiple>true</isMultiple> // </AttributeMapping> assertEquals(2, next.getProperties("name").size()); /** * Check normal in-line attribute mappings */ for (AttributeMapping attMapping : otherMappings) { Expression sourceExpr = attMapping.getSourceExpression(); // make sure the mapping has the right values if (!(sourceExpr instanceof AttributeExpressionImpl)) { // ignore attributes that aren't mapped from the input features, such as id continue; } AttributeExpressionImpl outputExpr = new AttributeExpressionImpl(attMapping .getTargetXPath().toString(), hints); Object inputValue = sourceExpr.evaluate(moFeature); while (inputValue instanceof Attribute) { inputValue = ((Attribute) inputValue).getValue(); } Object outputValue = outputExpr.evaluate(next); while (outputValue instanceof Attribute) { outputValue = ((Attribute) outputValue).getValue(); } assertEquals(inputValue, outputValue); } guFeatures.add(next); guCount++; } // make sure number of re-mapped features is consistent with input complex features assertEquals(inputFeatures.size(), guCount); /** * Feature chaining : Make sure the features can be chained as well. The re-mapped Geologic * Unit features are chained inside Mapped Features as specification. */ mfDataAccess.dispose(); // recreate mapped features from another mapping file to avoid circular reference Map<String, Serializable> dsParams = new HashMap<String, Serializable>(); URL url = getClass().getResource(schemaBase + "MappedFeaturePropertyfile.xml"); assertNotNull(url); dsParams.put("dbtype", "app-schema"); dsParams.put("url", url.toExternalForm()); mfDataAccess = DataAccessFinder.getDataStore(dsParams); assertNotNull(mfDataAccess); FeatureType mappedFeatureType = mfDataAccess.getSchema(MAPPED_FEATURE); assertNotNull(mappedFeatureType); FeatureSource<FeatureType, Feature> mfSource = mfDataAccess .getFeatureSource(MAPPED_FEATURE); FeatureCollection<FeatureType, Feature> mfCollection = mfSource.getFeatures(); Iterator<Feature> mfIterator = mfCollection.iterator(); while (mfIterator.hasNext()) { Feature mf = mfIterator.next(); Property spec = mf.getProperty("specification"); assertNotNull(spec); Object guObject = spec.getValue(); assertNotNull(guObject); assertTrue(guObject instanceof Collection); assertEquals(1, ((Collection<Feature>) guObject).size()); guObject = ((Collection<Feature>) guObject).iterator().next(); assertTrue(guObject instanceof Feature); Feature guFeature = (Feature) guObject; // make sure this is the re-mapped geologic unit feature assertTrue(guFeatures.contains(guFeature)); String propertyGuId = FeatureChainingTest.mfToGuMap.get(mf.getIdentifier().toString()) .split("gu.")[1]; assertEquals(((Feature) guObject).getIdentifier().toString(), propertyGuId); } mfCollection.close(mfIterator); mfDataAccess.dispose(); } /** * Test filters on the re-mapped geologic unit features, as well as the features that chain * them. * * @throws IOException */ @Override public void testFilters() throws IOException { // Filtering on re-mapped geologic unit features // Composition is a multi-valued property chained inside geologic unit. // We're testing that we can get a geologic unit which has a composition part with a // significant proportion value Expression property = ff .property("gsml:composition/gsml:CompositionPart/gsml:proportion/gsml:CGI_TermValue/gsml:value"); Filter filter = ff.like(property, "significant"); FeatureCollection<FeatureType, Feature> filteredResults = guFeatureSource .getFeatures(filter); // see CompositionPart.properties: // cp.167775491936278812=interbedded component|significant // cp.167775491936278844=interbedded component|significant // EarthResource.properties: // _=FORM:String,COMPOSITION:String // 25699=strataform|cp.167775491936278844,cp.167775491936278812,cp.167775491936278856 // 25682=cross-cutting|cp.167775491936278812 assertEquals(2, filteredResults.size()); // Filtering on mapped feature features that chain the re-mapped geologic unit features // First we need to recreate the mapping with a mapping file where gsml:specification exists mfDataAccess.dispose(); Map<String, Serializable> dsParams = new HashMap<String, Serializable>(); URL url = getClass().getResource(schemaBase + "MappedFeaturePropertyfile.xml"); assertNotNull(url); dsParams.put("dbtype", "app-schema"); dsParams.put("url", url.toExternalForm()); mfDataAccess = DataAccessFinder.getDataStore(dsParams); assertNotNull(mfDataAccess); FeatureSource<FeatureType, Feature> mfSource = mfDataAccess .getFeatureSource(MAPPED_FEATURE); property = ff .property("gsml:specification/gsml:GeologicUnit/gsml:bodyMorphology/gsml:CGI_TermValue/gsml:value"); filter = ff.like(property, "vein"); filteredResults = mfSource.getFeatures(filter); // see EarthResource.properties file: // _=FORM:String,COMPOSITION:String,CLASSIFICATION:String,COMMODITYDESCRIPTION:String // 25678=vein|cp.167775491936278856|urn:cgi:classifierScheme:GSV:GeologicalUnitType|mf2,mf3 // There are 2 mapped features: mf2 and mf3. // You can verify by looking at MappedFeaturePropertiesFile.properties as well assertEquals(2, filteredResults.size()); } /** * Non app-schema data access factory producing min-occ XML output. */ public static class MinOccDataAccessFactory extends InputDataAccessFactory { public DataAccess<? extends FeatureType, ? extends Feature> createDataStore( Map<String, Serializable> params) throws IOException { String schemaLocation = "/commonSchemas_new/mineralOccurrence/mineralOccurrence.xsd"; String typeName = EARTH_RESOURCE.getLocalPart(); URL schemaDir = (URL) params.get("directory"); File fileDir; try { fileDir = new File(schemaDir.toURI()); } catch (URISyntaxException e) { throw new RuntimeException(e); } PropertyDataStore dataStore = new PropertyDataStore(fileDir); FeatureSource<SimpleFeatureType, SimpleFeature> simpleFeatureSource = dataStore .getFeatureSource(typeName); // get the simple features from EarthResource.properties file FeatureCollection<SimpleFeatureType, SimpleFeature> fCollection = simpleFeatureSource .getFeatures(); reader = EmfAppSchemaReader.newInstance(); typeRegistry = new FeatureTypeRegistry(); // set catalog URL catalogLocation = getClass().getResource(schemaBase + "mappedPolygons.oasis.xml"); reader.setCatalog(CatalogUtilities.buildPrivateCatalog(catalogLocation)); SchemaIndex schemaIndex = reader.parse(new URL(schemaDir.toString() + schemaLocation), null); typeRegistry.addSchemas(schemaIndex); ComplexType complexType = (ComplexType) typeRegistry .getAttributeType(EARTH_RESOURCE_TYPE); inputFeatures = getInputFeatures(fCollection, complexType); return new InputDataAccess(inputFeatures, simpleFeatureSource.getSchema(), simpleFeatureSource.getName()); } public boolean canProcess(Map<String, Serializable> params) { Object dbType = params.get("dbtype"); return dbType == null ? false : dbType.equals("mo-data-access"); } } }