/* * GeoTools - The Open Source Java GIS Toolkit * http://geotools.org * * (C) 2009-2015, 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 java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.geotools.data.DataAccess; import org.geotools.data.DataSourceException; import org.geotools.data.DataStore; import org.geotools.data.FeatureSource; import org.geotools.data.Repository; import org.geotools.data.complex.config.Types; import org.geotools.factory.Hints; import org.geotools.util.InterpolationProperties; import org.geotools.util.logging.Logging; import org.opengis.feature.Feature; import org.opengis.feature.type.FeatureType; import org.opengis.feature.type.Name; import org.opengis.filter.identity.FeatureId; /** * A registry that stores data access instances per application. This allows feature sources from * different data accesses to be accessed globally. * * @author Rini Angreani (CSIRO Earth Science and Resource Engineering) * @author Niels Charlier (Curtin University Of Technology) * * * @source $URL$ */ public class DataAccessRegistry implements Repository { private static final long serialVersionUID = -373404928035022963L; private static final Logger LOGGER = Logging.getLogger(DataAccessRegistry.class); /** * Singleton instance */ protected static DataAccessRegistry theRegistry = null; /** * Properties for interpolation / configuration settings */ protected InterpolationProperties properties = null; /** * Data Access Resources */ protected List<DataAccess<FeatureType, Feature>> registry = new ArrayList<DataAccess<FeatureType, Feature>>(); /** * Sole constructor */ protected DataAccessRegistry() { } /** * Public method to get singleton instance to registry. * * @return An instance of this class */ public static DataAccessRegistry getInstance() { if (theRegistry == null) { theRegistry = new DataAccessRegistry(); } return theRegistry; } /** * Get a feature source for built features with supplied feature type name. * * @param featureTypeName * @return feature source * @throws IOException */ public synchronized FeatureSource<FeatureType, Feature> featureSource(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess.getNames().contains(name)) { if (dataAccess instanceof AppSchemaDataAccess) { return ((AppSchemaDataAccess) dataAccess).getFeatureSourceByName(name); } else { return dataAccess.getFeatureSource(name); } } } throwDataSourceException(name); return null; } public synchronized DataAccess<FeatureType, Feature> access(Name name) { try { return featureSource(name).getDataStore(); } catch (IOException e) { return null; } } public DataStore dataStore(Name name) { throw new UnsupportedOperationException("Simple feature DataStores not supported by app-schema registry."); } public List<DataStore> getDataStores() { throw new UnsupportedOperationException("Simple feature DataStores not supported by app-schema registry."); } /** * Registers a data access * * @param dataAccess * Data access to be registered */ public synchronized void registerAccess(DataAccess<FeatureType, Feature> dataAccess) { registry.add(dataAccess); } /** * Unregister a data access. This is important especially at the end of test cases, so that the * mappings contained in the data access do not conflict with mappings of the same type used in * other tests. * * @param dataAccess * Data access to be unregistered */ public synchronized void unregisterAccess(DataAccess<FeatureType, Feature> dataAccess) { registry.remove(dataAccess); if (dataAccess instanceof AppSchemaDataAccess) { AppSchemaDataAccess asda = (AppSchemaDataAccess) dataAccess; // NOTE: this code assumes hidden data accesses are never removed directly by the user, // only by the automatic disposal algorithm, so no need to run it again if (!asda.hidden) { try { disposeHiddenDataAccessInstances(); } catch (IOException e) { LOGGER.log(Level.SEVERE, "Exception occurred disposing unused data access instances", e); } } } } // utility method to clear up hidden app-schema data accesses (i.e. configured via a separate mapping file, specified in the <includedTypes> // directive of some top-level app-schema data access) that are no longer needed (i.e. they are not referenced by any top-level data access). private void disposeHiddenDataAccessInstances() throws IOException { // step 1: collect all hidden data access instances that are still referenced by some other data access boolean canSafelyRemove = true; Set<DataAccess<?, ?>> stillReferencedHiddenDataAccesses = new HashSet<DataAccess<?, ?>>(); for (DataAccess<FeatureType, Feature> da : registry) { if (da instanceof AppSchemaDataAccess) { AppSchemaDataAccess asda = (AppSchemaDataAccess) da; if (!asda.hidden) { // reach out to all referenced (directly or indirectly) DataAccesses Set<DataAccess<?, ?>> reachedDataAccesses = new HashSet<DataAccess<?, ?>>(); canSafelyRemove = canSafelyRemove && reachOutToReferencedDataAccesses(asda, stillReferencedHiddenDataAccesses, reachedDataAccesses); if (!canSafelyRemove) { break; } } } } // step 2: remove hidden data access instances that are no more referenced; // this step is performed only if no polymorphic nested mapping was found if (canSafelyRemove) { List<DataAccess<FeatureType, Feature>> copyRegistry = new ArrayList<DataAccess<FeatureType, Feature>>( registry); for (DataAccess<FeatureType, Feature> da : copyRegistry) { if (da instanceof AppSchemaDataAccess) { AppSchemaDataAccess asda = (AppSchemaDataAccess) da; if (asda.hidden && !stillReferencedHiddenDataAccesses.contains(asda)) { asda.dispose(); } } } } } // recursive method to navigate the dependency graph, following feature chaining links private boolean reachOutToReferencedDataAccesses(AppSchemaDataAccess asda, Set<DataAccess<?, ?>> stillReferencedDataAccessInstances, Set<DataAccess<?, ?>> reachedDataAccessInstances) throws IOException { reachedDataAccessInstances.add(asda); for (Name typeName : asda.getNames()) { FeatureTypeMapping ftm = asda.getMappingByNameOrElement(typeName); List<NestedAttributeMapping> nestedMappings = ftm.getNestedMappings(); if (nestedMappings != null) { for (NestedAttributeMapping nestedAttr : nestedMappings) { // TODO: can't figure out how to support polymorphic mappings without // evaluating the expression for every single feature, so, if a polymorphic // mapping is found, return false to notify the caller that automatic // disposal cannot be done safely if (!nestedAttr.isConditional()) { String nestedTypeNameAsString = nestedAttr.nestedFeatureType.toString(); Name nestedTypeName = Types.degloseName(nestedTypeNameAsString, nestedAttr.getNamespaces()); try { DataAccess<FeatureType, Feature> refDA = getDataAccess(nestedTypeName); if (refDA instanceof AppSchemaDataAccess) { AppSchemaDataAccess refASDA = (AppSchemaDataAccess) refDA; if (refASDA.hidden) { stillReferencedDataAccessInstances.add(refASDA); } if (!reachedDataAccessInstances.contains(refASDA)) { // recursive call if (!reachOutToReferencedDataAccesses(refASDA, stillReferencedDataAccessInstances, reachedDataAccessInstances)) { return false; } } } } catch (DataSourceException dse) { LOGGER.log(Level.FINER, "Referenced data access not found: " + "probably it has been removed already, moving on...", dse); } } else { LOGGER.finer("Polymorphic mapping found, disabling automatic disposal of hidden data accesses"); return false; } } } } return true; } /** * Dispose and unregister all data accesses in the registry. This is may be needed to prevent unit tests * from conflicting with data accesses with the same type name registered for other tests. */ public synchronized void disposeAndUnregisterAll() { List<DataAccess<FeatureType, Feature>> copyRegistry = new ArrayList<DataAccess<FeatureType, Feature>>(registry); for (DataAccess<FeatureType, Feature> da : copyRegistry) { da.dispose(); } registry.clear(); } /** * Return true if a type name is mapped in one of the registered data accesses. If * the type mapping has mappingName, then it will be the key that is matched in the search. If * it doesn't, then it will match the targetElementName. * * @param featureTypeName * Feature type name * @return * @throws IOException */ public synchronized boolean hasAccessName(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess.getNames().contains(name)) { return true; } } return false; } /** * Return true if a type name is mapped in one of the registered app-schema data accesses. If * the type mapping has mappingName, then it will be the key that is matched in the search. If * it doesn't, then it will match the targetElementName. * * @param featureTypeName * Feature type name * @return * @throws IOException */ public synchronized boolean hasAppSchemaAccessName(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess instanceof AppSchemaDataAccess && (((AppSchemaDataAccess) dataAccess).hasName(name) || ((AppSchemaDataAccess) dataAccess) .hasElement(name))) { return true; } } return false; } /** * Get a feature type mapping from a registered app-schema data access. Please note that this is * only possible for app-schema data access instances. * * @param featureTypeName * @return feature type mapping * @throws IOException */ public synchronized FeatureTypeMapping mappingByName(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess instanceof AppSchemaDataAccess) { if (((AppSchemaDataAccess) dataAccess).hasName(name)) { return ((AppSchemaDataAccess) dataAccess).getMappingByName(name); } } } throwDataSourceException(name); return null; } public synchronized FeatureTypeMapping mappingByElement(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess instanceof AppSchemaDataAccess) { if (((AppSchemaDataAccess) dataAccess).hasElement(name)) { return ((AppSchemaDataAccess) dataAccess).getMappingByNameOrElement(name); } } } throwDataSourceException(name); return null; } /** * Return true if a type name is mapped in one of the registered app-schema data accesses as * targetElementName, regardless whether or not mappingName exists. * * @param featureTypeName * @return * @throws IOException */ public synchronized boolean hasAppSchemaTargetElement(Name name) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (dataAccess instanceof AppSchemaDataAccess && Arrays.asList(((AppSchemaDataAccess) dataAccess).getTypeNames()).contains(name)) { return true; } } return false; } /** * Get properties * * @return properties */ public synchronized InterpolationProperties getProperties() { if (properties == null) { properties = new InterpolationProperties(AppSchemaDataAccessFactory.DBTYPE_STRING); } return properties; } /** * Clean-up properties, mainly used for cleaning up after tests */ public synchronized void clearProperties() { properties = null; } //------------------------------------------------------------------------------------- // Static short-cut methods for convenience and backward compatibility //------------------------------------------------------------------------------------- /** * Get a feature source for built features with supplied feature type name. * * @param featureTypeName * @return feature source * @throws IOException */ public static FeatureSource<FeatureType, Feature> getFeatureSource(Name featureTypeName) throws IOException { return getInstance().featureSource(featureTypeName); } public static DataAccess<FeatureType, Feature> getDataAccess(Name featureTypeName) throws IOException { return getInstance().featureSource(featureTypeName).getDataStore(); } /** * Registers a data access * * @param dataAccess * Data access to be registered */ public static void register(DataAccess<FeatureType, Feature> dataAccess) { getInstance().registerAccess(dataAccess); } /** * Unregister a data access. This is important especially at the end of test cases, so that the * mappings contained in the data access do not conflict with mappings of the same type used in * other tests. * Does not dispose * * This method should not be called directly, instead use dispose method from DataAccess * * @param dataAccess * Data access to be unregistered */ public static void unregister(DataAccess<FeatureType, Feature> dataAccess) { getInstance().unregisterAccess(dataAccess); } /** * Unregister * and dispose * all data accesses in the registry. This is may be needed to prevent unit tests * from conflicting with data accesses with the same type name registered for other tests. */ public static void unregisterAndDisposeAll() { getInstance().disposeAndUnregisterAll(); } /** * Unregister * and dispose * all data accesses in the registry. This is may be needed to prevent unit tests * from conflicting with data accesses with the same type name registered for other tests. * * @Deprecated use unregisterAndDisposeAll */ @Deprecated public static void unregisterAll() { getInstance().disposeAndUnregisterAll(); } /** * Return true if a type name is mapped in one of the registered data accesses. If * the type mapping has mappingName, then it will be the key that is matched in the search. If * it doesn't, then it will match the targetElementName. * * @param featureTypeName * Feature type name * @return * @throws IOException */ public static boolean hasName(Name featureTypeName) throws IOException { return getInstance().hasAccessName(featureTypeName); } //--------------------------------------------------------------------------------------- // helper methods //--------------------------------------------------------------------------------------- /** * Throws data source exception if mapping is not found. * * @param featureTypeName * Name of feature type * @throws IOException */ protected void throwDataSourceException(Name featureTypeName) throws IOException { List<Name> typeNames = new ArrayList<Name>(); for (Iterator<DataAccess<FeatureType, Feature>> dataAccessIterator = registry.iterator(); dataAccessIterator.hasNext();) { typeNames.addAll(dataAccessIterator.next().getNames()); } throw new DataSourceException("Feature type " + featureTypeName + " not found." + " Has the data access been registered in DataAccessRegistry?" + " Available: " + typeNames.toString()); } public Feature findFeature(FeatureId id, Hints hints) throws IOException { for (DataAccess<FeatureType, Feature> dataAccess : registry) { if (Thread.currentThread().isInterrupted()) { return null; } if (dataAccess instanceof AppSchemaDataAccess) { Feature feature = ((AppSchemaDataAccess) dataAccess).findFeature(id, hints); if (feature != null) { return feature; } } } return null; } }