package org.geotools.data.efeature; import static org.geotools.data.efeature.EFeatureDialect.EFEATURE_CONTEXT_ID; import static org.geotools.data.efeature.EFeatureDialect.EPACKAGE_NS_URI; import static org.geotools.data.efeature.EFeatureDialect.EDITING_DOMAIN_ID; import static org.geotools.data.efeature.EFeatureDialect.ERESOURCE_URI; import static org.geotools.data.efeature.EFeatureDialect.EFOLDERS_QUERY; import static org.geotools.data.efeature.EFeatureDialect.EFEATURE_WRITABLE; import java.awt.RenderingHints.Key; import java.io.IOException; import java.io.Serializable; import java.util.Collections; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.edit.domain.EditingDomain; import org.geotools.data.DataStore; import org.geotools.data.DataStoreFactorySpi; import org.geotools.factory.CommonFactoryFinder; import org.geotools.util.logging.Logging; import org.opengis.feature.simple.SimpleFeature; /** * A state-less {@link EFeatureDataStore} {@link DataStoreFactorySpi} class implementation. * <p> * This class adds {@link EFeatureDataStore} instance creation capabilities to the * GeoTools framework. The factory keeps no reference to created * {@link EFeatureDataStore}s, it each data store is given a reference to this factory instance. * </p> * * @author kengu * * @see {@link EFeatureDataStore#getDataStoreFactory()} * */ public class EFeatureDataStoreFactory implements DataStoreFactorySpi { // ----------------------------------------------------- // Public parameters // ----------------------------------------------------- /** * Statically cached {@link EFeatureDialect} instance. */ public static final EFeatureDialect DIALECT = new EFeatureDialect(); /** * {@link EFeatureContext} instance ID. * <p> */ public static final Param EFEATURE_CONTEXT_ID_PARAM = new Param( EFEATURE_CONTEXT_ID, String.class, "EFeatureContext instance ID", true); /** * {@link EditingDomain} instance ID. * <p> * All readers and writers are forces to use given {@link EditingDomain} instance for read/write * EMF model access. * <p> */ public static final Param EDITING_DOMAIN_ID_PARAM = new Param( EDITING_DOMAIN_ID, String.class, "Extension point id to an EditingDomain instance", true); /** * The name space URI of the {@link EPackage} which the {@link EClass} with name * {@link #EFOLDERS_QUERY} belongs. * <p> */ public static final Param EPACKAGE_NS_URI_PARAM = new Param( EPACKAGE_NS_URI, String.class, "The namespace URI of the EPackage which " + "defines EObjects containing EFeatures or EFeature compatible data", true); /** * {@link URI} to the {@link Resource} which the {@link EFeatureDataStore} instance fetches * {@link SimpleFeature}s from. * <p> * The {@link URI} points to a {@link Resource} managed by the {@link EditingDomain} specified * by {@link #EDITING_DOMAIN_ID}. * <p> * All readers and writers are forces to use given {@link EditingDomain} instance for read/write * access to the {@link Resource}. * <p> */ public static final Param ERESOURCE_URI_PARAM = new Param( ERESOURCE_URI, String.class, "URI to EMF Resource instance containing EFeatures or " + "EFeature compatible data managed by the editing domain", true); /** * A (optional) query that defines which {@link EFeature} folders to * include in a {@link EFeatureDataStore}. * <p> * This parameter has the following syntax: * * <pre> * eFolders=<eFolder1>+...+<eFolderN> * * where * * eFolder = <eName>[:<eQuery>|$<eFragment>] * </pre> */ public static final Param EFOLDERS_QUERY_PARAM = new Param(EFOLDERS_QUERY, String.class, "A query that defines which EFeature folders to include in a EFeatureStore.", false); /** * A boolean flag indication if {@link EFeature}s can be * written ({@link EFeatureWriter#UPDATE} | * {@link EFeatureWriter#APPEND}). */ public static final Param EFEATURE_WRITABLE_PARAM = new Param(EFEATURE_WRITABLE, Boolean.class, "A boolean flag indicating if EFeatures can be written (UPDATE | APPEND)", false); // ----------------------------------------------------- // Other static members // ----------------------------------------------------- /** * Static logger for all {@link EFeatureDataStoreFactory} instances */ private static final Logger LOGGER = Logging.getLogger(EFeatureDataStoreFactory.class); /** * Cached {@link EFeatureContextFactory} instance */ protected static EFeatureContextFactory eContextFactory; // ----------------------------------------------------- // Constructors // ----------------------------------------------------- /** * Public "no arguments" constructor called by Factory Service Provider * (SPI) based on entry in META-INF/services/org.geotools.data.DataStoreFactorySpi */ public EFeatureDataStoreFactory() { /*NOP*/ } // ----------------------------------------------------- // EFeatureDataStoreFactory methods // ----------------------------------------------------- @Override public String getDisplayName() { return "EGeometryFeature Data Store"; } @Override public String getDescription() { return "Allows access to EMF EObject instances containing JTS Geometry data"; } @Override public Param[] getParametersInfo() { return new Param[] { EFEATURE_CONTEXT_ID_PARAM, EDITING_DOMAIN_ID_PARAM, EPACKAGE_NS_URI_PARAM, ERESOURCE_URI_PARAM, EFOLDERS_QUERY_PARAM}; } /** * Check to see if {@link EFeatureDataStore}s can be created. * <p> * This factory is only available if at least one * {@link EFeatureContext} instance is registered. * <p> * * @return <code>true</code> if and only if this factory is * able to create {@link EFeatureDataStore}s. * */ @Override public boolean isAvailable() { return eGetContextFactory().isAvailable(); } @Override public Map<Key, ?> getImplementationHints() { // TODO: Add hints for CRS etc. // return Collections.emptyMap(); } @Override public boolean canProcess(Map<String, Serializable> params) { return canProcess(DIALECT,params); } /** * Construct a live {@link EFeatureDataStore} instance from given parameters. * * @param params - the full set of information needed to construct a live {@link EFeatureDataStore} * instance * * @return a new {@link EFeatureDataStore} instance, this may be <code>null</code> if the required * resource was not found or if insufficient parameters were given. Note that * canProcess() should have returned false if the problem is to do with insufficient * parameters. * * @throws IOException if there were any problems setting up (creating or connecting) the * {@link EFeatureContext}, {@link EditingDomain} or {@link EObject} container * instances. * @throws IllegalArgumentException if resolved {@link EObject} container and * {@link EFeaturePackageInfo} does not match. */ @Override public EFeatureDataStore createDataStore(Map<String, Serializable> params) throws IOException, IllegalArgumentException { // // lookup will throw error message for // lack of required parameter or wrong data type // String eContextID = (String) EFEATURE_CONTEXT_ID_PARAM.lookUp(params); String eDomainID = (String) EDITING_DOMAIN_ID_PARAM.lookUp(params); String eNsURI = (String) EPACKAGE_NS_URI_PARAM.lookUp(params); String eURI = (String) ERESOURCE_URI_PARAM.lookUp(params); String eFolders = (String) EFOLDERS_QUERY_PARAM.lookUp(params); boolean eWritable = toValue((Boolean) EFEATURE_WRITABLE_PARAM.lookUp(params),Boolean.class,true); // // Try processing parameters first so we can get real IO // error message back to the user // if (!canProcess(params)) { throw new IOException("One or more parameters are " + "missing or invalid. See log for more information."); } // // Construct EFeatureStore instance from given parameters // EFeatureDataStore eDataStore = new EFeatureDataStore(eContextID, eDomainID, eNsURI, eURI, eFolders, eWritable); // // Set filter factory ( // eDataStore.setFilterFactory(CommonFactoryFinder.getFilterFactory(null)); // // Set reference to this data store factory // eDataStore.setDataStoreFactory(this); // // Finished // return eDataStore; } /** * TODO: Create a new {@link EObject} container and construct a live data {@link EFeatureDataStore} * instance on it from given parameters. * <p> * For now, this method is forwarded to {@link #createDataStore(Map)}. * <p> * * @param params - the full set of information needed to construct a live {@link EFeatureDataStore} * instance * * @return a new {@link EFeatureDataStore} instance, this may be <code>null</code> if the required * resource was not found or if insufficient parameters were given. Note that * canProcess() should have returned false if the problem is to do with insufficient * parameters. * * @throws IOException if there were any problems setting up (creating or connecting) the * {@link EFeatureContext}, {@link EditingDomain} or {@link EObject} container * instances. * @throws IllegalArgumentException if resolved {@link EObject} container and * {@link EFeaturePackageInfo} does not match. */ @Override public DataStore createNewDataStore(Map<String, Serializable> params) throws IOException, IllegalArgumentException { return createDataStore(params); } /** * Get {@link EFeature} folder names from given {@link EFeatureContext} * * @param eContextID - {@link EFeatureContext} instance extension point id * @param eNsURI - {@link EPackage} defining {@link EObject}s containing {@link EFeature}s or * {@link EFeature} compatible data. * @param eQuery - query used to select which {@link EFeature} folders to include * @return an array of {@link EFeature} folder names if found, <code>empty</code> array * otherwise. */ public static String[] getFolderNames(String eContextID, String eNsURI, String eQuery) { EFeatureContextInfo eInfo = eGetContextFactory().eStructure(eContextID); if (eInfo != null) { EFeaturePackageInfo d = eInfo.eGetPackageInfo(eNsURI); if (d != null) { return d.eGetFolderNames(eQuery); } } return new String[] {}; } /** * Get current {@link EFeature} {@link EFeatureContext context} {@link EFeatureContextFactory factory} */ public static EFeatureContextFactory eGetContextFactory() { if(eContextFactory==null) { eContextFactory = EFeatureContextFactory.eDefault(); } return eContextFactory; } /** * Set current {@link EFeature} {@link EFeatureContext context} {@link EFeatureContextFactory factory} */ public static void eSetContextFactory(EFeatureContextFactory eFactory) { eContextFactory = eFactory; } /** * Get {@link EFeaturePackageInfo} instance with given * {@link EFeaturePackageInfo#eNsURI() namespace URL} * from {@link EFeatureContext} with given * {@link EFeatureContext#eContextID() ID}. * </p> * * @param eContextID - given context {@link EFeatureContext#eContextID() ID} * @param eNsURI - {@link EPackage} defining {@link EObject}s containing {@link EFeature}s or * {@link EFeature} compatible data. * @return a {@link EFeaturePackageInfo} if found. * @throws IllegalArgumentException If no {@link EFeaturePackageInfo} instance was found. */ public static EFeaturePackageInfo ePackageInfo(String eContextID, String eNsURI) { EFeatureContextInfo eContextInfo = eGetContextFactory().eStructure(eContextID); return eContextInfo.eGetPackageInfo(eNsURI); } /** * Get {@link EditingDomain} instance with given ID * from {@link EFeatureContext} with given {@link EFeatureContext#eContextID() ID}. * </p> * @param eContextID - given context {@link EFeatureContext#eContextID() ID} * @param eDomainID - given {@link EditingDomain} ID * @return a {@link EditingDomain} instance if found. * @throws IllegalArgumentException If no {@link EditingDomain} instance was found. */ public static EditingDomain eDomain(String eContextID, String eDomainID) { EFeatureContext eContext = eGetContextFactory().eContext(eContextID); return eContext.eGetDomain(eDomainID); } /** * Get {@link EditingDomain} instance with given ID in the {@link EFeatureContext} * with given {@link EFeatureContext#eContextID() ID}. * </p> * @param eContextID - given context {@link EFeatureContext#eContextID() ID} * @param eDomainID - given {@link EditingDomain} ID * @return a {@link EditingDomain} instance if found. * @throws NullPointerException If 'eContext' is <code>null</code>. * @throws IllegalArgumentException If no {@link EditingDomain} instance was found. */ public static EditingDomain eDomain(EFeatureContext eContext, String eDomainID) { if(eContext==null) { throw new NullPointerException("EFeatureContext can not be 'null'"); } return eContext.eGetDomain(eDomainID); } /** * Check if {@link EFeatureDataStoreFactory} can process given parameters * * @param params - {@link EFeatureDataStore} connection parameters */ private static boolean canProcess(EFeatureDialect dialect, Map<String, Serializable> params) { if (params != null) { // lookup will throw error message for // lack of required parameter or wrong data type // String eContextID; String eDomainID; String eNsURI; URI eURI; String eFolders; try { eContextID = (String) EFEATURE_CONTEXT_ID_PARAM.lookUp(params); eDomainID = (String) EDITING_DOMAIN_ID_PARAM.lookUp(params); eNsURI = (String) EPACKAGE_NS_URI_PARAM.lookUp(params); eURI = URI.createURI((String) ERESOURCE_URI_PARAM.lookUp(params)); eFolders = (String) EFOLDERS_QUERY_PARAM.lookUp(params); } catch (IOException e) { LOGGER.log(Level.WARNING, e.getMessage(), e); return false; } // // Check if required all exist // if (params.containsKey(EFEATURE_CONTEXT_ID) && params.containsKey(EDITING_DOMAIN_ID) && params.containsKey(ERESOURCE_URI)) { EditingDomain eDomain = eDomain(eContextID, eDomainID); if (eDomain != null) { if(params.containsKey(EFOLDERS_QUERY)) { if( !(eFolders==null || eFolders.length()==0)) { for (String eFolder : dialect.toFolderQueries(eFolders)) { EFeaturePackageInfo eInfo = ePackageInfo(eContextID,eNsURI); if (eInfo == null) return false; String eURIFragment = dialect.getFolderFragment(eFolder); if (!(eURIFragment == null || eURIFragment.length() == 0)) { URI uri = eURI.appendFragment(eURIFragment); if (eDomain.getResourceSet().getEObject(uri, true) == null) return false; } } } } return true; } } } return false; } private static <T> T toValue(Object v, Class<T> type, T d) { return (v==null ? d : type.cast(v)); } }