org.geotools.data.store.ContentDataStore Maven / Gradle / Ivy
Show all versions of gt-main Show documentation
/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011-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.store;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
import org.geotools.data.DataAccess;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DefaultServiceInfo;
import org.geotools.data.FeatureReader;
import org.geotools.data.FeatureWriter;
import org.geotools.data.InProcessLockingManager;
import org.geotools.data.LockingManager;
import org.geotools.data.Query;
import org.geotools.data.ServiceInfo;
import org.geotools.data.Transaction;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureTypes;
import org.geotools.feature.NameImpl;
import org.locationtech.jts.geom.GeometryFactory;
import org.opengis.feature.FeatureFactory;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.FeatureTypeFactory;
import org.opengis.feature.type.Name;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.util.TypeName;
/**
* Abstract base class for data stores.
*
* A datastore contains a set of entries ({@link ContentEntry}). Each entry corresponds to a
* "real world dataset". For instance, a shapefile datastore would contain a single entry which
* would represent the shapefile on disk. A postgis datastore could contain many entries, one for
* each table in the database.
*
*
Each entry is identified by a name ({@link Name}). The name can be qualified with a namespace
* uri, or unqualified (in which the namespace uri is null). An example of a datastore that might
* use qualified names is WFS, where in each entry corresponds to a WFS "Feature Type", which have
* namespace qualified name. Other datastores (such as databases) use unqualified names.
*
*
When entry names of a datastore are unqualified, a default namespace uri can be set "globally"
* on the datastore itself, see {@link #setNamespaceURI(String)}. When this value is set,
* unqualified entry names are implicitly qualified with the global namespace uri.
*
*
Subclasses
*
* At a minimum subclasses must implement the following methods:
*
*
* - {@link #createTypeNames()}
*
- {@link #createFeatureSource(ContentEntry)}
*
*
* The following methods may also be overriden:
*
*
* - {@link #createContentState(ContentEntry)}
*
*
* The following methods may be overriden but only to narrow the return type to a specific
* subclass of {@link ContentFeatureSource}.
*
*
* - {@link #getFeatureSource(String)}
*
- {@link #getFeatureSource(String, Transaction)}
*
- {@link #getFeatureSource(Name, Transaction)}
*
*
* @author Jody Garnett, Refractions Research Inc.
* @author Justin Deoliveira, The Open Planning Project
* @author Niels Charlier
*/
public abstract class ContentDataStore implements DataStore {
/**
* When joining feature types, the UserData of the joined attribute descriptors will contain
* their full feature type under this key
*/
public static final String JOINED_FEATURE_TYPE = "JoinedFeatureType";
/** Flag writer for adding new content */
protected static final int WRITER_ADD = 0x01 << 0;
/** Flag writer for updating content in place */
protected static final int WRITER_UPDATE = 0x01 << 1;
/** Flag writer for commit (AUTO_COMMIT with no events) */
protected static final int WRITER_COMMIT = 0x01 << 2;
/** name, entry map */
protected final Map entries;
/** logger */
protected final Logger LOGGER;
/** Factory used to create feature types */
protected FeatureTypeFactory typeFactory;
/** Factory used to create features */
protected FeatureFactory featureFactory;
/** Factory used to create filters */
protected FilterFactory filterFactory;
/** Factory used to create geometries */
protected GeometryFactory geometryFactory;
/** namespace uri of the datastore itself, or default namespace */
protected String namespaceURI;
/** locking manager */
protected LockingManager lockingManager = new InProcessLockingManager();
/** factory used to create the datastore */
protected DataStoreFactorySpi dataStoreFactory;
public ContentDataStore() {
// get a concurrent map so that we can do reads in parallel with writes (writes vs writes
// are actually synchronized to prevent double work, see getEntry()).
this.entries = new ConcurrentHashMap();
// grabbing the logger here makes the logger name polymorphic (the name of the actual
// subclass will be used
this.LOGGER = org.geotools.util.logging.Logging.getLogger(getClass());
// default
setFilterFactory(CommonFactoryFinder.getFilterFactory());
}
//
// Property accessors
//
/** The factory used to create feature types. */
public FeatureTypeFactory getFeatureTypeFactory() {
return typeFactory;
}
/** Sets the factory used to create feature types. */
public void setFeatureTypeFactory(FeatureTypeFactory typeFactory) {
this.typeFactory = typeFactory;
}
/** Sets the factory used to create features. */
public void setFeatureFactory(FeatureFactory featureFactory) {
this.featureFactory = featureFactory;
}
/** The factory used to create filters. */
public FilterFactory getFilterFactory() {
return filterFactory;
}
/** The factory used to create features. */
public FeatureFactory getFeatureFactory() {
return featureFactory;
}
/** Sets the factory used to create filters. */
public void setFilterFactory(FilterFactory filterFactory) {
this.filterFactory = filterFactory;
}
/** The factory used to create geometries. */
public GeometryFactory getGeometryFactory() {
return geometryFactory;
}
/** Sets the factory used to create geometries. */
public void setGeometryFactory(GeometryFactory geometryFactory) {
this.geometryFactory = geometryFactory;
}
/**
* Returns the factory used to create the data store.
*
* @return The data store factory, possibly null
.
*/
public DataStoreFactorySpi getDataStoreFactory() {
return dataStoreFactory;
}
/**
* Sets the data store factory used to create the datastore.
*
* WARNING: This property should only be set in cases where the datastore factory is
* stateless and does not maintain any references to created datastores. Setting this property
* in such a case will result in a memory leak.
*/
public void setDataStoreFactory(DataStoreFactorySpi dataStoreFactory) {
this.dataStoreFactory = dataStoreFactory;
}
/**
* The namespace uri of the datastore.
*
* @return The namespace uri, may be null
.
*/
public String getNamespaceURI() {
return namespaceURI;
}
/**
* Sets the namespace uri of the datastore.
*
*
This will be used to qualify the entries or types of the datastore.
*
* @param namespaceURI The namespace uri, may be null
.
*/
public void setNamespaceURI(String namespaceURI) {
this.namespaceURI = namespaceURI;
}
/** The logger for the datastore. */
public Logger getLogger() {
return LOGGER;
}
//
// DataStore API
//
public ServiceInfo getInfo() {
DefaultServiceInfo info = new DefaultServiceInfo();
info.setDescription("Features from " + getClass().getSimpleName());
info.setSchema(FeatureTypes.DEFAULT_NAMESPACE);
return info;
}
/**
* Returns the names of all entries or types provided by the datastore.
*
*
This method is marked final and delegates to {@link #createTypeNames()}, which subclasses
* are intended to implement.
*
* @see DataStore#getTypeNames()
*/
public final String[] getTypeNames() throws IOException {
List typeNames = createTypeNames();
String[] names = new String[typeNames.size()];
for (int i = 0; i < typeNames.size(); i++) {
Name typeName = typeNames.get(i);
names[i] = typeName.getLocalPart();
}
return names;
}
/**
* Creates a new schema in the datastore.
*
* This implementation throws a{@link UnsupportedOperationException}. Subclasses should
* override to support schema creation.
*
* @see DataStore#createSchema(FeatureType)
*/
public void createSchema(SimpleFeatureType featureType) throws IOException {
throw new UnsupportedOperationException();
}
/**
* Returns the feature type or schema matching the specified name.
*
*
This method calls through to getFeatureSource(typeName).getSchema()
*
* @see DataStore#getSchema(String)
*/
public final SimpleFeatureType getSchema(String typeName) throws IOException {
ContentFeatureSource featureSource = getFeatureSource(typeName);
return featureSource.getSchema();
}
/**
* Returns the feature source matching the specified name.
*
*
Subclasses should not implement this method. However overriding in order to perform a type
* narrowing to a subclasses of {@link ContentFeatureSource} is acceptable.
*
* @see DataStore#getFeatureSource(String)
*/
public ContentFeatureSource getFeatureSource(String typeName) throws IOException {
return getFeatureSource(new NameImpl(null, typeName), Transaction.AUTO_COMMIT);
}
/**
* Returns the feature source matching the specified name and explicitly specifies a
* transaction.
*
*
Subclasses should not implement this method. However overriding in order to perform a type
* narrowing to a subclasses of {@link ContentFeatureSource} is acceptable.
*
* @see DataStore#getFeatureSource(String)
*/
public ContentFeatureSource getFeatureSource(String typeName, Transaction tx)
throws IOException {
return getFeatureSource(new NameImpl(null, typeName), tx);
}
/**
* Returns the feature source matching the specified name and explicitly specifies a
* transaction.
*
*
Subclasses should not implement this method. However overriding in order to perform a type
* narrowing to a subclasses of {@link ContentFeatureSource} is acceptable.
*
* @see DataStore#getFeatureSource(String)
*/
public ContentFeatureSource getFeatureSource(Name typeName, Transaction tx) throws IOException {
ContentEntry entry = ensureEntry(typeName);
ContentFeatureSource featureSource = createFeatureSource(entry);
featureSource.setTransaction(tx);
// if ( tx != Transaction.AUTO_COMMIT ) {
// //setup the transaction state
// synchronized (tx) {
// if ( tx.getState( typeName ) == null ) {
// tx.putState( typeName, createTransactionState(featureSource) );
// }
// }
// }
return featureSource;
}
/**
* Returns a feature reader for the specified query and transaction.
*
*
This method is not intended to be overridden and is marked final. This implementation
* delegates to {@link FeatureCollection} and wraps an iterator in a {@link FeatureReader}.
*/
public FeatureReader getFeatureReader(
Query query, Transaction tx) throws IOException {
if (query.getTypeName() == null) {
throw new IllegalArgumentException("Query does not specify type.");
}
return getFeatureSource(query.getTypeName(), tx).getReader(query);
}
/**
* Returns a feature writer for the specified query and transaction.
*
* This method is not intended to be overridden and is marked final. This implementation
* delegates to {@link FeatureCollection} and wraps an iterator in a {@link FeatureWriter}.
*/
public FeatureWriter getFeatureWriter(
String typeName, Filter filter, Transaction tx) throws IOException {
ContentFeatureStore featureStore = ensureFeatureStore(typeName, tx);
return featureStore.getWriter(filter, WRITER_UPDATE | WRITER_ADD);
}
/**
* Helper method which gets a feature source ensuring that it is a feature store as well. If not
* it throws an IOException.
*
* @param typeName The name of the feature source.
* @param tx A transaction handle.
* @throws IOException If the feature source is not a store.
*/
protected final ContentFeatureStore ensureFeatureStore(String typeName, Transaction tx)
throws IOException {
ContentFeatureSource featureSource = getFeatureSource(typeName, tx);
if (!(featureSource instanceof ContentFeatureStore)) {
throw new IOException(typeName + " is read only");
}
return (ContentFeatureStore) featureSource;
}
/**
* Returns a feature writer for the specified type name and transaction.
*
* This method is convenience for getFeatureWriter(typeName,Filter.INCLUDE,tx)
.
*/
public final FeatureWriter getFeatureWriter(
String typeName, Transaction tx) throws IOException {
return getFeatureWriter(typeName, Filter.INCLUDE, tx);
}
/**
* Returns an appending feature writer for the specified type name and transaction.
*
* This method is not intended to be overridden and is marked final. This implementation
* delegates to {@link FeatureCollection} and wraps an iterator in a {@link FeatureWriter}.
*/
public final FeatureWriter getFeatureWriterAppend(
String typeName, Transaction tx) throws IOException {
ContentFeatureStore featureStore = ensureFeatureStore(typeName, tx);
FeatureWriter writer =
featureStore.getWriter(Filter.INCLUDE, WRITER_ADD);
// ensure we are at the "end" as we are being asked to return this in "append" mode
while (writer.hasNext()) {
writer.next();
}
return writer;
}
public final LockingManager getLockingManager() {
return lockingManager;
}
public final void updateSchema(String typeName, SimpleFeatureType featureType)
throws IOException {
throw new UnsupportedOperationException();
}
public void dispose() {
for (ContentEntry entry : entries.values()) {
entry.dispose();
}
entries.clear();
}
/** Returns the entry for a specified name, or null
if no such entry exists. */
public ContentEntry getEntry(Name name) {
return entries.get(name);
}
//
// Internal API
//
/**
* Instantiates a new content state for the entry.
*
* Subclasses may override this method to return a specific subclass of {@link ContentState}.
*
* @param entry The entry.
* @return A new instance of {@link ContentState} for the entry.
*/
protected ContentState createContentState(ContentEntry entry) {
return new ContentState(entry);
}
/** Helper method to wrap a non-qualified name. */
protected final Name name(String typeName) {
return new NameImpl(namespaceURI, typeName);
}
/**
* Helper method to look up an entry in the datastore.
*
*
This method will create a new instance of {@link ContentEntry} if one does not exist.
*
*
In the event that the name does not map to an entry and one cannot be created null
*
will be returned. Note that {@link #ensureEntry(TypeName)} will throw an exception in
* this case.
*
* @param name The name of the entry.
* @return The entry, or null
if it does not exist.
*/
protected final ContentEntry entry(Name name) throws IOException {
ContentEntry entry = null;
boolean found = entries.containsKey(name);
boolean unqualifiedSearch = name.getNamespaceURI() == null;
if (!found && unqualifiedSearch && this.namespaceURI != null) {
Name defaultNsName = new NameImpl(namespaceURI, name.getLocalPart());
if (entries.containsKey(defaultNsName)) {
name = defaultNsName;
found = true;
}
}
if (!found) {
// refresh the entries (calling createTypeNames() can be quite expensive,
// make it count) and do namespace-less matches as required
List typeNames = createTypeNames();
for (Name tn : typeNames) {
synchronized (this) {
if (!entries.containsKey(tn)) {
entry = new ContentEntry(this, tn);
entries.put(tn, entry);
}
}
// do namespace-less matching if necessary
if (!found
&& (tn.equals(name)
|| (unqualifiedSearch
&& tn.getLocalPart().equals(name.getLocalPart())))) {
name = tn;
found = true;
}
}
}
return entries.get(name);
}
/**
* Helper method to look up an entry in the datastore which throws an {@link IOException} in the
* event that the entry does not exist.
*
* @param name The name of the entry.
* @return The entry.
* @throws IOException If the entry does not exist, or if there was an error looking it up.
*/
protected final ContentEntry ensureEntry(Name name) throws IOException {
ContentEntry entry = entry(name);
if (entry == null) {
throw new IOException("Schema '" + name + "' does not exist.");
}
return entry;
}
/**
* Helper method to remove an entry from the cached entry map.
*
* @param name The name of the entry.
*/
protected final void removeEntry(Name name) {
if (entries.containsKey(name)) {
entries.remove(name);
}
}
/**
* Creates a set of qualified names corresponding to the types that the datastore provides.
*
* Namespaces may be left null
for data stores which do not support namespace
* qualified type names.
*
* @return A list of {@link Name}.
* @throws IOException Any errors occuring connecting to data.
*/
protected abstract List createTypeNames() throws IOException;
/**
* Instantiates new feature source for the entry.
*
* Subclasses should override this method to return a specific subclass of {@link
* ContentFeatureSource}.
*
* @param entry The entry.
* @return An new instance of {@link ContentFeatureSource} for the entry.
*/
protected abstract ContentFeatureSource createFeatureSource(ContentEntry entry)
throws IOException;
/**
* Instantiates a new transaction state object.
*
*
Subclasses should override method to return a specific instance of {@link
* Transaction.State}.
*
* @param SimpleFeatureSource The feature source / store for the new transaction state.
*/
// protected abstract Transaction.State createTransactionState(ContentSimpleFeatureSource
// featureSource)
// throws IOException;
/**
* Delegates to {@link #getFeatureSource(Name, Transaction)}
*
* @since 2.5
* @see DataAccess#getFeatureSource(Name)
*/
public SimpleFeatureSource getFeatureSource(Name typeName) throws IOException {
return getFeatureSource(typeName, Transaction.AUTO_COMMIT);
}
/**
* Returns the same list of names than {@link #getTypeNames()} meaning the returned Names have
* no namespace set.
*
* @since 2.5
* @see DataAccess#getNames()
*/
public List getNames() throws IOException {
String[] typeNames = getTypeNames();
List names = new ArrayList(typeNames.length);
for (String typeName : typeNames) {
names.add(new NameImpl(typeName));
}
return names;
}
/**
* Delegates to {@link #getSchema(String)} with {@code name.getLocalPart()}
*
* @since 2.5
* @see DataAccess#getSchema(Name)
*/
public SimpleFeatureType getSchema(Name name) throws IOException {
return getSchema(name.getLocalPart());
}
/**
* Delegates to {@link #updateSchema(String, SimpleFeatureType)} with {@code
* name.getLocalPart()}
*
* @since 2.5
* @see DataAccess#getFeatureSource(Name)
*/
public void updateSchema(Name typeName, SimpleFeatureType featureType) throws IOException {
updateSchema(typeName.getLocalPart(), featureType);
}
/** @see DataAccess#removeSchema(Name) */
public void removeSchema(Name typeName) throws IOException {
throw new UnsupportedOperationException("Schema removal not supported");
}
/** @see DataStore#removeSchema(String) */
public void removeSchema(String typeName) throws IOException {
throw new UnsupportedOperationException("Schema removal not supported");
}
}