//$HeadURL$
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2008 by:
Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
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; either
version 2.1 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.igeo.dataadapter.wfs;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.deegree.datatypes.QualifiedName;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.HttpUtils;
import org.deegree.framework.util.StringTools;
import org.deegree.framework.xml.XMLFragment;
import org.deegree.framework.xml.XMLParsingException;
import org.deegree.framework.xml.XMLTools;
import org.deegree.igeo.ApplicationContainer;
import org.deegree.igeo.dataadapter.DataAccessException;
import org.deegree.igeo.dataadapter.FeatureAdapter;
import org.deegree.igeo.dataadapter.FileFeatureAdapter;
import org.deegree.igeo.dataadapter.OWSURLUtils;
import org.deegree.igeo.i18n.Messages;
import org.deegree.igeo.mapmodel.Datasource;
import org.deegree.igeo.mapmodel.Layer;
import org.deegree.igeo.mapmodel.MapModel;
import org.deegree.igeo.mapmodel.WFSDatasource;
import org.deegree.igeo.settings.Settings;
import org.deegree.igeo.settings.WFSFeatureAdapterSettings;
import org.deegree.model.crs.GeoTransformer;
import org.deegree.model.feature.Feature;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.feature.schema.GMLSchema;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.GeometryException;
import org.deegree.ogcwebservices.wfs.operation.GetFeature;
import org.deegree.ogcwebservices.wfs.operation.Query;
import org.xml.sax.SAXException;
/**
* Implementation of {@link FeatureAdapter} for accessing data from a WFS
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class WFSFeatureAdapter extends FeatureAdapter {
private static final ILogger LOG = LoggerFactory.getLogger( FileFeatureAdapter.class );
private boolean isLazyLoading;
private URL describeFTURL;
private URL getFeatureURL;
private URL transactionURL;
private String version;
private WFSDataLoader loader;
private WFSFeatureAdapterSettings wfsAda;
private Envelope lastEnv;
private static Map<String, XMLFragment> capabilitiesCache;
private URL baseURL;
static {
if ( capabilitiesCache == null ) {
capabilitiesCache = new HashMap<String, XMLFragment>();
}
}
/**
*
* @param datasource
* @param layer
* @param mapModel
* @param baseURL
* @param isLazyLoading
*/
public WFSFeatureAdapter( Datasource datasource, Layer layer, MapModel mapModel, URL baseURL, boolean isLazyLoading ) {
super( datasource, layer, mapModel );
this.baseURL = baseURL;
this.isLazyLoading = isLazyLoading;
Settings settings = mapModel.getApplicationContainer().getSettings();
wfsAda = settings.getWFSFeatureAdapter();
// first try using baseURL for performing requests
useBaseURL( baseURL );
instantiateLoader();
refresh();
}
/**
*
* @param baseURL
*/
private void useBaseURL( URL baseURL ) {
try {
URL url = OWSURLUtils.normalizeOWSURL( baseURL );
transactionURL = url;
getFeatureURL = url;
describeFTURL = url;
} catch ( MalformedURLException e ) {
LOG.logError( e );
}
}
/**
* loads the GML application schema assigned with the feature types of a WFS datasource
*
*/
private void loadSchema() {
GetFeature gf = ( (WFSDatasource) this.datasource ).getGetFeature();
Query query = gf.getQuery()[0];
if ( schemas.get( datasource.getName() ) == null ) {
try {
// read GML application schema if not already has been loaded
QualifiedName[] qn = query.getTypeNames();
GMLSchema schema = loader.readGMLApplicationSchema( describeFTURL, layer, qn );
schemas.put( datasource.getName(), schema.getFeatureType( qn[0] ) );
} catch ( Exception e ) {
if ( describeFTURL == null ) {
// read target URLs (and WFS version) from WFS capabilities
readURLs( baseURL );
loadSchema();
} else {
throw new DataAccessException( e );
}
}
}
}
/**
* reads destination URLs for GetFeature, DescribeFeatureType and Transaction requests from WFS capabilities
* document
*
* @param baseURL
* @return
*/
private void readURLs( URL baseURL ) {
XMLFragment xml;
String capabilitiesUrl = null;
try {
ApplicationContainer<?> appCont = mapModel.getApplicationContainer();
capabilitiesUrl = baseURL.toURI().toASCIIString();
String tmp = HttpUtils.normalizeURL( capabilitiesUrl );
capabilitiesUrl = HttpUtils.addAuthenticationForKVP( capabilitiesUrl, appCont.getUser(),
appCont.getPassword(), appCont.getCertificate( tmp ) );
if ( capabilitiesCache.containsKey( capabilitiesUrl ) ) {
xml = capabilitiesCache.get( capabilitiesUrl );
LOG.logInfo( "read capabilities from cache: ", capabilitiesUrl );
} else {
InputStream is = HttpUtils.performHttpGet( capabilitiesUrl, null, wfsAda.getTimeout(),
appCont.getUser(), appCont.getPassword(), null ).getResponseBodyAsStream();
xml = new XMLFragment();
xml.load( is, baseURL.toExternalForm() );
capabilitiesCache.put( capabilitiesUrl, xml );
}
} catch ( SAXException e ) {
LOG.logError( e.getMessage(), e );
String s = StringTools.stackTraceToString( e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10013", capabilitiesUrl )
+ "\n" + s, e );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
String s = StringTools.stackTraceToString( e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10012", capabilitiesUrl )
+ "\n" + s );
}
try {
version = XMLTools.getRequiredAttrValue( "version", null, xml.getRootElement() );
} catch ( XMLParsingException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10014", capabilitiesUrl,
xml.getAsPrettyString() ) );
}
String className = wfsAda.getCapabilitiesEvaluator( version );
Class<?> clzz = null;
try {
clzz = Class.forName( className );
} catch ( ClassNotFoundException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10017", className ) );
}
WFSCapabilitiesEvaluator evaluator = null;
try {
evaluator = (WFSCapabilitiesEvaluator) clzz.newInstance();
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10018", className ) );
}
evaluator.setCapabilities( xml );
try {
getFeatureURL = evaluator.getGetFeatureURL();
describeFTURL = evaluator.getDescribeFeatureTypeURL();
transactionURL = evaluator.getTransactionURL();
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( e.getMessage(), e );
}
}
/**
* loads features described by the adapted WFS datasource
*
*/
private void loadData() {
fireStartLoadingEvent();
GetFeature gf = ( (WFSDatasource) datasource ).getGetFeature();
Query[] queries = gf.getQuery();
QualifiedName geomProperty = ( (WFSDatasource) datasource ).getGeometryProperty();
Envelope currentEnv = null;
if ( this.isLazyLoading ) {
currentEnv = mapModel.getEnvelope();
} else {
currentEnv = mapModel.getMaxExtent();
}
// transform current envelope into native CRS of the data source if required
if ( !mapModel.getCoordinateSystem().equals( datasource.getNativeCoordinateSystem() ) ) {
GeoTransformer gt = new GeoTransformer( datasource.getNativeCoordinateSystem() );
try {
currentEnv = gt.transform( currentEnv, mapModel.getCoordinateSystem().getPrefixedName(), true );
} catch ( Exception e ) {
LOG.logError( e );
throw new DataAccessException( e.getMessage() );
}
}
Envelope env = null;
if ( featureCollections.get( datasource.getName() ) != null ) {
// if a fc is already available take its BBOX
try {
env = featureCollections.get( datasource.getName() ).getBoundedBy();
} catch ( GeometryException e ) {
fireLoadingExceptionEvent();
throw new DataAccessException( e );
}
}
for ( Query query : queries ) {
// load data from WFS
FeatureCollection fc = loader.readFeatureCollection( getFeatureURL, geomProperty, currentEnv, query, layer );
fc = transformToMapModelCrs( fc );
try {
if ( env == null ) {
env = fc.getBoundedBy();
} else if ( fc.getBoundedBy() != null ) {
env = env.merge( fc.getBoundedBy() );
}
} catch ( Exception e ) {
fireLoadingExceptionEvent();
throw new DataAccessException( e );
}
if ( featureCollections.get( datasource.getName() ) == null ) {
featureCollections.put( datasource.getName(), fc );
} else {
featureCollections.get( datasource.getName() ).addAllUncontained( fc );
}
}
// set envelope of the datasource
if ( env == null ) {
env = layer.getOwner().getEnvelope();
}
datasource.setExtent( env );
fireLoadingFinishedEvent();
}
/**
* initializes the loader class used with a WFSFeatureAdapter. Because the concrete loader depends on version of
* connected WFS, class name will be read from deegree configuration and instantiated using reflection API
*
*/
private void instantiateLoader() {
String className = wfsAda.getDataLoader( version );
Class<?> clzz = null;
try {
clzz = Class.forName( className );
} catch ( ClassNotFoundException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10026", className ) );
}
try {
this.loader = (WFSDataLoader) clzz.newInstance();
this.loader.setTimeout( wfsAda.getTimeout() );
this.loader.setMaxFeatures( wfsAda.getMaxFeatures() );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10027", className ) );
}
}
/**
* initializes the write class used with a WFSFeatureAdapter. Because the concrete loader depends on version of
* connected WFS, class name will be read from deegree configuration and instantiated using reflection API
*
*/
private WFSDataWriter instantiateWriter() {
String className = wfsAda.getDataWriter( version );
Class<?> clzz = null;
try {
clzz = Class.forName( className );
} catch ( ClassNotFoundException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10026", className ) );
}
WFSDataWriter writer;
try {
writer = (WFSDataWriter) clzz.newInstance();
writer.setTimeout( wfsAda.getTimeout() );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10027", className ) );
}
return writer;
}
@Override
public FeatureCollection getFeatureCollection() {
if ( this.isLazyLoading ) {
synchronized ( datasource ) {
double min = datasource.getMinScaleDenominator();
double max = datasource.getMaxScaleDenominator();
if ( mapModel.getScaleDenominator() >= min && mapModel.getScaleDenominator() < max ) {
Envelope currentEnv = mapModel.getEnvelope();
if ( lastEnv == null || ( !currentEnv.equals( lastEnv ) && !lastEnv.contains( currentEnv ) ) ) {
refreshData();
lastEnv = mapModel.getEnvelope();
// perform all inserts, updates, deletes that has been performed on this
// data source adapter on the feature collection read from the adapted data source
updateFeatureCollection();
}
}
}
}
return featureCollections.get( datasource.getName() );
}
/*
* (non-Javadoc)
*
* @see org.deegree.client.presenter.connector.IMapModelAdapter#refresh()
*/
public void refresh() {
if ( featureCollections.get( datasource.getName() ) == null & !this.isLazyLoading ) {
// if feature collection has already been loaded for a not lazy loading data source
// it don't have to loaded again.
refreshData();
}
loadSchema();
}
private void refreshData() {
try {
loadData();
} catch ( Exception e ) {
if ( getFeatureURL == null ) {
// read target URLs (and WFS version) from WFS capabilities
readURLs( baseURL );
loadData();
} else {
throw new DataAccessException( e );
}
}
}
@Override
public void refresh( boolean forceReload ) {
if ( forceReload ) {
refreshData();
} else {
refresh();
}
}
@Override
public void commitChanges()
throws IOException {
if ( transactionURL != null ) {
try {
WFSDataWriter writer = instantiateWriter();
List<FeatureAdapter.Changes> changeList = changes.get( datasource.getName() );
if ( changeList != null ) {
FeatureCollection fc = getInsertCollection( changeList );
List<String> newIDs = writer.insertFeatures( transactionURL, fc, layer );
// set IDs generated by the WFS when inserting new features to ensure consistent feature
// IDs on server (WFS) and client
Feature[] features = fc.toArray();
for ( int i = 0; i < features.length; i++ ) {
features[i].setId( newIDs.get( i ) );
}
fc = getUpdateCollection( changeList );
writer.updateFeatures( transactionURL, fc, layer );
fc = getDeleteCollection( changeList );
writer.deleteFeatures( transactionURL, fc, layer );
}
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new IOException( e.getMessage() );
} finally {
changes.get( datasource.getName() ).clear();
}
} else {
throw new IOException( Messages.getMessage( Locale.getDefault(), "$DG10085", layer.getTitle() ) );
}
}
}