/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2012, 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.wfs.impl;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.xml.namespace.QName;
import org.geotools.data.DataAccess;
import org.geotools.data.FeatureSource;
import org.geotools.data.ServiceInfo;
import org.geotools.data.complex.config.EmfComplexFeatureReader;
import org.geotools.data.complex.config.FeatureTypeRegistry;
import org.geotools.data.wfs.internal.DescribeFeatureTypeRequest;
import org.geotools.data.wfs.internal.WFSClient;
import org.geotools.feature.NameImpl;
import org.geotools.feature.type.ComplexFeatureTypeFactoryImpl;
import org.geotools.xml.SchemaIndex;
import org.geotools.gml3.complex.GmlFeatureTypeRegistryConfiguration;
import org.geotools.xml.resolver.SchemaCache;
import org.geotools.xml.resolver.SchemaResolver;
import org.opengis.feature.Feature;
import org.opengis.feature.type.AttributeDescriptor;
import org.opengis.feature.type.FeatureType;
import org.opengis.feature.type.Name;
/**
* @author Adam Brown (Curtin University of Technology)
* Inspired by code from WFSContentDataStore & ContentDataStore.
*/
public class WFSContentDataAccess implements DataAccess<FeatureType, Feature> {
/**
* The Web feature service client object.
*/
private final WFSClient client;
/**
* The schema reader used to take describe feature URL and turn it into a
* schema index.
*/
private EmfComplexFeatureReader schemaParser;
/**
* Collection of feature type descriptors.
*/
private FeatureTypeRegistry typeRegistry;
/**
* Backing field for the cache location file.
*/
private File cacheLocation;
/**
* A map of names and their QName equivalent.
*/
private final Map<Name, QName> names;
/**
* namespace URL of the datastore itself, or default namespace
*/
protected String namespaceURI;
/**
* The namespace URL of the datastore.
*
* @return The namespace URL, may be <code>null</code>.
*/
public String getNamespaceURI() {
return namespaceURI;
}
/**
* Sets the namespace URI of the datastore.
* <p>
* This will be used to qualify the entries or types of the datastore.
* </p>
*
* @param namespaceURI
* The namespace URI, may be <code>null</code>.
*/
public void setNamespaceURI(String namespaceURI) {
this.namespaceURI = namespaceURI;
}
/**
* The WFS capabilities document.
*
* @param capabilities
*/
public WFSContentDataAccess(final WFSClient client) {
this.client = client;
this.names = new ConcurrentHashMap<Name, QName>();
}
@Override
public ServiceInfo getInfo() {
return this.client.getInfo();
}
@Override
public void createSchema(FeatureType featureType) throws IOException {
throw new UnsupportedOperationException(
"WFSContentDataAccess does not support createSchema.");
}
@Override
public void updateSchema(Name typeName, FeatureType featureType)
throws IOException {
throw new UnsupportedOperationException(
"WFSContentDataAccess does not support update schema.");
}
/**
* Populates the names map and returns a list of names.
*/
@Override
public List<Name> getNames() throws IOException {
// the WFSContentDataStore version inherits an implementation of this
// method from ContentDataStore, that method calls getTypeNames which
// calls an abstract method (i.e. one that's implemented in
// WFSContentDataStore) called createTypeNames(). createTypeNames, as
// implemented in WFSContentDataStore, uses client to
// 'getRemoteTypeNames()'.
Set<QName> remoteTypeNames = client.getRemoteTypeNames();
List<Name> namesList = new ArrayList<Name>(remoteTypeNames.size());
for (QName remoteTypeName : remoteTypeNames) {
Name typeName = new NameImpl(remoteTypeName);
namesList.add(typeName);
this.names.put(typeName, remoteTypeName);
}
return namesList;
}
/**
* Look up a QName based on a given Name.
* @param localTypeName
* The local type name whose QName equivalent you'd like.
* @return
* The QName that corresponds to the Name you passed in.
* @throws IOException
*/
public QName getRemoteTypeName(Name localTypeName) throws IOException {
if (names.isEmpty()) {
getNames();
}
QName qName = names.get(localTypeName);
if (null == qName) {
throw new NoSuchElementException(localTypeName.toString());
}
return qName;
}
@Override
public FeatureType getSchema(Name name) throws IOException {
// If there are no values in this.names it probably means that getNames
// hasn't been called yet.
if (this.names.size() == 0) {
this.getNames();
}
// Generate the URL for the feature request:
// -----------------------------------------
DescribeFeatureTypeRequest describeFeatureTypeRequest = client
.createDescribeFeatureTypeRequest();
QName qname = this.names.get(name);
describeFeatureTypeRequest.setTypeName(qname);
URL describeRequestURL = describeFeatureTypeRequest.getFinalURL();
// Create type registry and add the schema to it:
// ----------------------------------------------
FeatureTypeRegistry typeRegistry = this.getFeatureTypeRegistry();
SchemaIndex schemaIndex = this.getSchemaParser().parse(
describeRequestURL);
typeRegistry.addSchemas(schemaIndex);
// Create the attribute type and cast it as a FeatureType:
// -------------------------------------------------------
AttributeDescriptor attributeDescriptor = typeRegistry.getDescriptor(
name, null);
return (FeatureType) attributeDescriptor.getType();
}
/**
* Sets the location of the cache folder to be used by app-schema-resolver.
*
* @param cacheLocation
* the folder to use as the cache.
*/
public void setCacheLocation(File cacheLocation) {
this.cacheLocation = cacheLocation;
}
@Override
public FeatureSource<FeatureType, Feature> getFeatureSource(Name typeName)
throws IOException {
// There is an implementation of this in ContentDataStore which gets
// inherited by WFSContentDataStore.
FeatureSource<FeatureType, Feature> contentComplexFeatureSource = new WFSContentComplexFeatureSource(
typeName, this.client, this);
return contentComplexFeatureSource;
}
@Override
public void dispose() {
this.schemaParser = null;
this.typeRegistry = null;
}
/**
* Get the schema parser, creating it first if necessary.
*
* @return the schema parser. Guaranteed non-null.
*/
private EmfComplexFeatureReader getSchemaParser() {
if (this.schemaParser == null) {
this.schemaParser = EmfComplexFeatureReader.newInstance();
SchemaResolver appSchemaResolver;
if (this.cacheLocation == null) {
appSchemaResolver = new SchemaResolver();
} else {
appSchemaResolver = new SchemaResolver(new SchemaCache(this.cacheLocation,
/* download: */true,
/* keepQuery: */true));
}
this.schemaParser.setResolver(appSchemaResolver);
}
return this.schemaParser;
}
/**
* Get the type registry, creating it first if necessary.
*
* @return the type registry. Guaranteed non-null.
*/
private FeatureTypeRegistry getFeatureTypeRegistry() {
if (this.typeRegistry == null) {
this.typeRegistry = new FeatureTypeRegistry(new ComplexFeatureTypeFactoryImpl(),
new GmlFeatureTypeRegistryConfiguration(null));
}
return this.typeRegistry;
}
@Override
public void removeSchema(Name typeName) throws IOException {
throw new UnsupportedOperationException(
"WFSContentDataAccess does not support remove schema.");
}
}