//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/ogcwebservices/wfs/GetFeatureHandler.java,v 1.29 2006/11/16 08:53:21 mschneider Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, 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.ogcwebservices.wfs;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import org.deegree.datatypes.QualifiedName;
import org.deegree.framework.concurrent.ExecutionFinishedEvent;
import org.deegree.framework.concurrent.Executor;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.i18n.Messages;
import org.deegree.io.datastore.schema.MappedFeatureType;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.ogcwebservices.OGCWebServiceException;
import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
import org.deegree.ogcwebservices.wfs.capabilities.WFSOperationsMetadata;
import org.deegree.ogcwebservices.wfs.configuration.WFSConfiguration;
import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
import org.deegree.ogcwebservices.wfs.operation.GetFeature;
import org.deegree.ogcwebservices.wfs.operation.Query;
import org.deegree.ogcwebservices.wfs.operation.GetFeature.RESULT_TYPE;
import org.deegree.owscommon.OWSDomainType;
/**
* Handles {@link GetFeature} requests to the {@link WFService}. Since a {@link GetFeature}
* request may contain more than one {@link Query}, each {@link Query} is delegated to an own
* thread.
* <p>
* The results of all threads are collected and merged before they are returned to the calling
* {@link WFService} as a single {@link FeatureCollection}.
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author <a href="mailto:schneider@lat-lon.de">Markus Schneider</a>
* @author last edited by: $Author: mschneider $
*
* @version $Revision: 1.29 $, $Date: 2006/11/16 08:53:21 $
*/
class GetFeatureHandler {
private static final ILogger LOG = LoggerFactory.getLogger( GetFeatureHandler.class );
private static final String EPSG_URL = "http://www.opengis.net/gml/srs/epsg.xml#";
// upper limit for timeout (overrides WFS configuration)
private static long MAX_TIMEOUT_MILLIS = 10 * 60 * 1000;
private WFService service;
private int maxFeatures = -1;
/**
* Creates a new instance of <code>GetFeatureHandler</code>. Only called by the
* <code>WFService</code>.
*
* @param service
* associated WFService
*/
GetFeatureHandler( WFService service ) {
this.service = service;
WFSCapabilities capa = service.getCapabilities();
WFSOperationsMetadata md = (WFSOperationsMetadata) capa.getOperationsMetadata();
OWSDomainType[] dt = md.getConstraints();
for ( int i = 0; i < dt.length; i++ ) {
if ( dt[i].getName().equals( "DefaultMaxFeatures" ) ) {
try {
String tmp = dt[i].getValues()[0];
maxFeatures = Integer.parseInt( tmp );
} catch ( Exception e ) {
//e.printStackTrace();
}
break;
}
}
LOG.logDebug( "default maxFeatures " + maxFeatures );
}
/**
* Handles a {@link GetFeature} request by delegating the contained {@link Query} objects to
* different Threads.
* <p>
* If at least one query fails an exception will be thrown and all running threads will be
* stopped.
*
* @param getFeature
* @return result of the request
* @throws OGCWebServiceException
*/
FeatureResult handleRequest( GetFeature getFeature )
throws OGCWebServiceException {
LOG.entering();
if ( getFeature.getMaxFeatures() > maxFeatures ) {
getFeature.setMaxFeatures( maxFeatures );
}
LOG.logDebug( "maxFeatures " + getFeature.getMaxFeatures() );
Query[] queries = getFeature.getQuery();
List<Callable<FeatureCollection>> queryTasks = new ArrayList<Callable<FeatureCollection>>(
queries.length );
for ( Query query : queries ) {
QualifiedName[] ftNames = query.getTypeNames();
// TODO joins between feature types
if ( ftNames.length > 1 ) {
String msg = "Multiple feature types in a Query (joins over feature types) "
+ "are not the supported by this WFS implementation.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
QualifiedName ftName = ftNames[0];
MappedFeatureType ft = this.service.getMappedFeatureType( ftName );
if ( ft == null ) {
String msg = Messages.getMessage( "WFS_FEATURE_TYPE_UNKNOWN", ftName );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
if ( !ft.isVisible() ) {
String msg = Messages.getMessage( "WFS_FEATURE_TYPE_INVISIBLE", ftName );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
// check and normalized requested SRS
String srsName = query.getSrsName();
if ( srsName != null ) {
WFSFeatureType wfsFT = this.service.getCapabilities().getFeatureTypeList().getFeatureType(
ftName );
String normalizedSrsName = normalizeSrsName( srsName );
query.setSrsName(normalizedSrsName);
if ( !( wfsFT.supportsSrs( normalizedSrsName ) ) ) {
String msg = Messages.getMessage( "WFS_FEATURE_TYPE_SRS_UNSUPPORTED", ftName, srsName );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
}
QueryTask task = new QueryTask( query, ft );
queryTasks.add( task );
}
WFSConfiguration conf = (WFSConfiguration) service.getCapabilities();
long timeout = conf.getDeegreeParams().getRequestTimeLimit() * 1000;
if ( timeout > MAX_TIMEOUT_MILLIS ) {
// limit max timeout
timeout = MAX_TIMEOUT_MILLIS;
}
List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents = null;
try {
finishedEvents = Executor.getInstance().performSynchronously( queryTasks, timeout );
} catch ( InterruptedException e ) {
String msg = "Exception occured while waiting for the GetFeature results: "
+ e.getMessage();
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
// use id of the request as id of the result feature collection
// to allow identification of the original request that produced
// the feature collection
FeatureCollection fc = null;
if ( getFeature.getResultType() == RESULT_TYPE.RESULTS ) {
fc = mergeResults( getFeature.getId(), finishedEvents );
} else {
fc = mergeHits( getFeature.getId(), finishedEvents );
}
FeatureResult fr = new FeatureResult( getFeature, fc );
LOG.exiting();
return fr;
}
/**
* Merges the results of the request subparts into one feature collection.
*
* @param fcid
* id of the new (result) feature collection
* @param finishedEvents
* @return feature collection containing all features from all responses
* @throws OGCWebServiceException
*/
private FeatureCollection mergeResults(
String fcid,
List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents )
throws OGCWebServiceException {
FeatureCollection result = null;
try {
for ( ExecutionFinishedEvent<FeatureCollection> event : finishedEvents ) {
if ( result == null ) {
result = event.getResult();
} else {
result.addAll( event.getResult() );
}
}
} catch ( CancellationException e ) {
String msg = Messages.getMessage( "WFS_GET_FEATURE_TIMEOUT" );
LOG.logInfo( msg );
throw new OGCWebServiceException( this.getClass().getName(), msg );
} catch ( Throwable t ) {
String msg = Messages.getMessage( "WFS_GET_FEATURE_BACKEND", t.getMessage() );
LOG.logError( msg, t );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
result.setId( fcid );
result.setAttribute( "numberOfFeatures", "" + result.size() );
return result;
}
/**
* Merges the results of the request subparts into one feature collection.
* <p>
* This method is used if only the HITS have been requested, i.e. the number of features.
*
* TODO: Do this a better way (maybe change feature model).
*
* @param fcid
* id of the new (result) feature collection
* @param finishedEvents
* @return empty feature collection with "numberOfFeatures" attribute
* @throws OGCWebServiceException
*/
private FeatureCollection mergeHits(
String fcid,
List<ExecutionFinishedEvent<FeatureCollection>> finishedEvents )
throws OGCWebServiceException {
FeatureCollection result = null;
int numberOfFeatures = 0;
try {
for ( ExecutionFinishedEvent<FeatureCollection> event : finishedEvents ) {
FeatureCollection fc = event.getResult();
try {
numberOfFeatures += Integer.parseInt( ( fc.getAttribute( "numberOfFeatures" ) ) );
} catch ( NumberFormatException e ) {
String msg = "Internal error. Could not parse 'numberOfFeatures' attribute "
+ "of sub-result as an integer value.";
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
if ( result == null ) {
result = fc;
} else {
result.addAll( fc );
}
}
} catch ( CancellationException e ) {
String msg = Messages.getMessage( "WFS_GET_FEATURE_TIMEOUT" );
LOG.logInfo( msg );
throw new OGCWebServiceException( this.getClass().getName(), msg );
} catch ( Throwable t ) {
String msg = Messages.getMessage( "WFS_GET_FEATURE_BACKEND", t.getMessage() );
LOG.logError( msg );
throw new OGCWebServiceException( this.getClass().getName(), msg );
}
result.setId( fcid );
result.setAttribute( "numberOfFeatures", "" + numberOfFeatures );
return result;
}
/**
* Returns a normalized version of the given srs identifier.
* <p>
* Names in the format: <code>http://www.opengis.net/gml/srs/epsg.xml#XYZ</code> are returned
* as <code>EPSG:XYZ</code>.
*
* @param srsName
* name of the srs, <code>EPSG:xyz</code>
* @return a normalized version of <code>srsName</code>
*/
private String normalizeSrsName( String srsName ) {
String normalizedName = srsName;
if ( srsName.startsWith( EPSG_URL ) ) {
String epsgCode = srsName.substring( EPSG_URL.length() );
normalizedName = "EPSG:" + epsgCode;
}
return normalizedName;
}
// ///////////////////////////////////////////////////////////////////////////
// inner classes //
// ///////////////////////////////////////////////////////////////////////////
/**
* Inner class for performing queries on a mapped feature type (datastore).
*/
private class QueryTask implements Callable<FeatureCollection> {
private Query query;
private MappedFeatureType ft;
QueryTask( Query query, MappedFeatureType ft ) {
this.query = query;
this.ft = ft;
}
/**
* Performs the associated {@link Query} and returns the result.
*
* @return resulting feature collection
* @throws Exception
*/
public FeatureCollection call()
throws Exception {
FeatureCollection result = this.ft.performQuery( this.query );
return result;
}
}
}
/* **************************************************************************************************
* Changes to this class. What the people have been up to:
*
* $Log: GetFeatureHandler.java,v $
* Revision 1.29 2006/11/16 08:53:21 mschneider
* Merged messages from org.deegree.ogcwebservices.wfs and its subpackages.
*
* Revision 1.28 2006/11/09 17:46:15 mschneider
* Added check for srsName in queries.
*
* Revision 1.27 2006/10/17 13:13:13 schmitz
* Added the last missing type parameters.
*
* Revision 1.26 2006/10/09 12:49:02 poth
* bug fix - setting new ID to merged feature collections
*
* Revision 1.25 2006/08/30 18:10:17 mschneider
* Improved javadoc.
*
* Revision 1.24 2006/08/14 16:47:54 mschneider
* Improved error logging.
*
* Revision 1.23 2006/08/14 13:16:32 mschneider
* Moved magic number (timeout) to constant.
*
* Revision 1.22 2006/08/14 13:02:57 mschneider
* Adapted to use new concurrent API (Executor).
*
* Revision 1.21 2006/06/29 11:33:39 poth
* logging changed
*
* Revision 1.20 2006/06/01 15:19:16 mschneider
* Fixed footer.
*
* Revision 1.19 2006/05/29 16:47:08 mays
* changes by AP in mergeResults()
*
* Revision 1.18 2006/05/26 09:44:01 poth
* *** empty log message ***
*
* Revision 1.17 2006/04/06 20:25:21 poth
* *** empty log message ***
*
* Revision 1.16 2006/03/30 21:20:23 poth
* *** empty log message ***
*
* Revision 1.15 2006/02/23 13:15:40 poth
* *** empty log message ***
*
* Revision 1.14 2006/02/05 21:22:32 mschneider
* Added handling for invisible feature types.
*
* Revision 1.13 2006/02/02 20:42:54 mschneider
* Hail to the style guide.
*
* Revision 1.12 2006/01/08 14:09:35 poth
* *** empty log message ***
*
* Revision 1.11 2005/12/15 08:51:58 poth
* no message
*
* Revision 1.10 2005/12/13 14:37:55 poth
* no message
*
* Revision 1.9 2005/12/04 14:45:45 poth
* no message
*
* Revision 1.8 2005/11/30 07:36:05 deshmukh
* *** empty log message ***
*
* Revision 1.7 2005/11/17 15:35:07 deshmukh
* *** empty log message ***
*
* Revision 1.6 2005/11/16 13:44:59 mschneider
* Merge of wfs development branch.
*
* Revision 1.5.2.1 2005/11/08 15:32:23 mschneider
* Refactored to new feature type model.
*
* Revision 1.5 2005/09/27 19:53:19 poth
* no message
*
* Revision 1.4 2005/09/02 15:15:45 taddei
* removed println
*
* Revision 1.3 2005/09/01 13:00:46 mschneider
* Changes due to correction of "XXXDatstore" names to "XXXDatastore".
*
* Revision 1.2 2005/08/26 21:27:18 pot
* no message
*
* Revision 1.1 2005/08/26 21:11:29 poth
* no message
*
* Revision 1.16 2005/08/24 16:09:53 mschneider
* Renamed GenericName to QualifiedName.
*
* Revision 1.15 2005/08/19 07:45:11 poth
* no message
*
* Revision 1.14 2005/08/09 15:48:24 poth
* no message
*
* Revision 1.13 2005/08/02 15:01:21 mschneider
* Improved exception handling (still not done).
*
************************************************************************************************* */