001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wms/DefaultGetFeatureInfoHandler.java $
002    /*----------------------------------------------------------------------------
003     This file is part of deegree, http://deegree.org/
004     Copyright (C) 2001-2009 by:
005       Department of Geography, University of Bonn
006     and
007       lat/lon GmbH
008    
009     This library is free software; you can redistribute it and/or modify it under
010     the terms of the GNU Lesser General Public License as published by the Free
011     Software Foundation; either version 2.1 of the License, or (at your option)
012     any later version.
013     This library is distributed in the hope that it will be useful, but WITHOUT
014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016     details.
017     You should have received a copy of the GNU Lesser General Public License
018     along with this library; if not, write to the Free Software Foundation, Inc.,
019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020    
021     Contact information:
022    
023     lat/lon GmbH
024     Aennchenstr. 19, 53177 Bonn
025     Germany
026     http://lat-lon.de/
027    
028     Department of Geography, University of Bonn
029     Prof. Dr. Klaus Greve
030     Postfach 1147, 53001 Bonn
031     Germany
032     http://www.geographie.uni-bonn.de/deegree/
033    
034     e-mail: info@deegree.org
035    ----------------------------------------------------------------------------*/
036    package org.deegree.ogcwebservices.wms;
037    
038    import static java.lang.System.currentTimeMillis;
039    import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetFeatureInfoResponse;
040    
041    import java.awt.Color;
042    import java.awt.image.BufferedImage;
043    import java.io.BufferedReader;
044    import java.io.ByteArrayInputStream;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.io.InputStreamReader;
048    import java.io.Reader;
049    import java.io.StringReader;
050    import java.net.URI;
051    import java.net.URL;
052    import java.util.ArrayList;
053    import java.util.Arrays;
054    import java.util.Iterator;
055    import java.util.LinkedList;
056    import java.util.List;
057    import java.util.Map;
058    import java.util.concurrent.Callable;
059    import java.util.concurrent.CancellationException;
060    
061    import org.deegree.datatypes.QualifiedName;
062    import org.deegree.datatypes.Types;
063    import org.deegree.datatypes.UnknownTypeException;
064    import org.deegree.datatypes.values.Values;
065    import org.deegree.framework.concurrent.DoDatabaseQueryTask;
066    import org.deegree.framework.concurrent.DoExternalAccessTask;
067    import org.deegree.framework.concurrent.DoServiceTask;
068    import org.deegree.framework.concurrent.ExecutionFinishedEvent;
069    import org.deegree.framework.concurrent.Executor;
070    import org.deegree.framework.log.ILogger;
071    import org.deegree.framework.log.LoggerFactory;
072    import org.deegree.framework.util.CharsetUtils;
073    import org.deegree.framework.util.IDGenerator;
074    import org.deegree.framework.util.MapUtils;
075    import org.deegree.framework.util.NetWorker;
076    import org.deegree.framework.xml.NamespaceContext;
077    import org.deegree.framework.xml.XMLFragment;
078    import org.deegree.framework.xml.XMLTools;
079    import org.deegree.framework.xml.XSLTDocument;
080    import org.deegree.graphics.transformation.GeoTransform;
081    import org.deegree.graphics.transformation.WorldToScreenTransform;
082    import org.deegree.i18n.Messages;
083    import org.deegree.model.coverage.grid.ImageGridCoverage;
084    import org.deegree.model.crs.CRSFactory;
085    import org.deegree.model.crs.CoordinateSystem;
086    import org.deegree.model.crs.GeoTransformer;
087    import org.deegree.model.feature.Feature;
088    import org.deegree.model.feature.FeatureCollection;
089    import org.deegree.model.feature.FeatureFactory;
090    import org.deegree.model.feature.FeatureProperty;
091    import org.deegree.model.feature.GMLFeatureCollectionDocument;
092    import org.deegree.model.feature.schema.FeatureType;
093    import org.deegree.model.feature.schema.PropertyType;
094    import org.deegree.model.filterencoding.ComplexFilter;
095    import org.deegree.model.filterencoding.FeatureFilter;
096    import org.deegree.model.filterencoding.FeatureId;
097    import org.deegree.model.filterencoding.Filter;
098    import org.deegree.model.spatialschema.Envelope;
099    import org.deegree.model.spatialschema.GMLGeometryAdapter;
100    import org.deegree.model.spatialschema.Geometry;
101    import org.deegree.model.spatialschema.GeometryFactory;
102    import org.deegree.ogcbase.CommonNamespaces;
103    import org.deegree.ogcbase.InvalidSRSException;
104    import org.deegree.ogcbase.PropertyPath;
105    import org.deegree.ogcwebservices.OGCWebServiceException;
106    import org.deegree.ogcwebservices.OGCWebServiceRequest;
107    import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
108    import org.deegree.ogcwebservices.wfs.RemoteWFService;
109    import org.deegree.ogcwebservices.wfs.WFService;
110    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
111    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
112    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
113    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
114    import org.deegree.ogcwebservices.wfs.operation.Query;
115    import org.deegree.ogcwebservices.wms.capabilities.Dimension;
116    import org.deegree.ogcwebservices.wms.capabilities.Layer;
117    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
118    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
119    import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
120    import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource;
121    import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
122    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
123    import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
124    import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess;
125    import org.deegree.ogcwebservices.wms.operation.DimensionValues;
126    import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
127    import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult;
128    import org.deegree.ogcwebservices.wms.operation.GetMap;
129    import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
130    import org.deegree.processing.raster.converter.Image2RawData;
131    import org.w3c.dom.Document;
132    
133    /**
134     *
135     *
136     *
137     * @version $Revision: 18195 $
138     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
139     * @author last edited by: $Author: mschneider $
140     *
141     * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
142     *
143     */
144    class DefaultGetFeatureInfoHandler implements GetFeatureInfoHandler {
145    
146        protected static final ILogger LOG = LoggerFactory.getLogger( DefaultGetFeatureInfoHandler.class );
147    
148        private static final double DEFAULT_PIXEL_SIZE = 0.00028;
149    
150        protected GetFeatureInfo request = null;
151    
152        protected GetMap getMapRequest = null;
153    
154        protected WMSConfigurationType configuration = null;
155    
156        // collects the reponse for each layer
157        private Object[] featCol = null;
158    
159        // scale of the map
160        protected double scale = 0;
161    
162        // CRS of the request
163        protected CoordinateSystem reqCRS = null;
164    
165        protected static final QualifiedName VALUE = new QualifiedName( "value" );
166    
167        /**
168         * Creates a new GetMapHandler object.
169         *
170         * @param capabilities
171         * @param request
172         *            request to perform
173         * @throws OGCWebServiceException
174         */
175        public DefaultGetFeatureInfoHandler( WMSConfigurationType capabilities, GetFeatureInfo request )
176                                throws OGCWebServiceException {
177    
178            this.request = request;
179            this.configuration = capabilities;
180            getMapRequest = request.getGetMapRequestCopy();
181            try {
182                reqCRS = CRSFactory.create( getMapRequest.getSrs() );
183                if ( reqCRS == null ) {
184                    throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) );
185                }
186            } catch ( Exception e ) {
187                throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) );
188            }
189            try {
190                Envelope bbox = getMapRequest.getBoundingBox();
191                double pixelSize = 1;
192                if ( request.getVersion().equals( "1.3.0" ) ) {
193                    // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
194                    // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
195                    pixelSize = DEFAULT_PIXEL_SIZE;
196                }
197                scale = MapUtils.calcScale( getMapRequest.getWidth(), getMapRequest.getHeight(), bbox, reqCRS, pixelSize );
198            } catch ( Exception e ) {
199                LOG.logDebug( e.getLocalizedMessage(), e );
200                throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC", e ) );
201            }
202    
203        }
204    
205        /**
206         * performs a GetFeatureInfo request and retruns the result encapsulated within a <tt>WMSFeatureInfoResponse</tt>
207         * object.
208         * <p>
209         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
210         * imposible to return a result. If something wents wrong performing the request (none fatal error) The exception
211         * shall be encapsulated within the response object to be returned to the client as requested
212         * (GetFeatureInfo-Request EXCEPTION-Parameter).
213         *
214         * <p>
215         * All sublayers of the queried layer will be added automatically. Non-queryable sublayers are then ignored in the
216         * response.
217         * </p>
218         *
219         * @return response to the GetFeatureInfo response
220         */
221        public GetFeatureInfoResult performGetFeatureInfo()
222                                throws OGCWebServiceException {
223            long runningTime = currentTimeMillis();
224    
225            String[] qlayers = request.getQueryLayers();
226    
227            List<Layer> allLayers = new ArrayList<Layer>();
228    
229            // here, the explicitly queried layers are checked for being queryable and known
230            for ( int i = 0; i < qlayers.length; i++ ) {
231                Layer layer = configuration.getLayer( qlayers[i] );
232    
233                if ( layer == null ) {
234                    throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", qlayers[i] ) );
235                }
236                if ( !layer.isQueryable() ) {
237                    throw new LayerNotQueryableException( Messages.getMessage( "WMS_LAYER_NOT_QUERYABLE", qlayers[i] ) );
238                }
239                if ( !layer.isSrsSupported( getMapRequest.getSrs() ) ) {
240                    throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER",
241                                                                        getMapRequest.getSrs(), qlayers[i] ) );
242                }
243    
244                allLayers.add( layer );
245    
246                // sub layers are added WITHOUT being checked for being queryable
247                // This is desirable for example in the following scenario:
248                // Suppose one queryable layer contains a lot of other layers,
249                // that are mostly queryable. Then you can query all of those layers
250                // at once by just querying the enclosing layer (the unqueryable
251                // sub layers are ignored).
252                allLayers.addAll( Arrays.asList( layer.getLayer() ) );
253            }
254    
255            Layer[] layerList = allLayers.toArray( new Layer[allLayers.size()] );
256    
257            if ( layerList.length == 1 && layerList[0].getDataSource().length == 1
258                 && layerList[0].getDataSource()[0].getFeatureInfoURL() != null ) {
259                LOG.logDebug( "GetFeatureInfo predefined response workaround enabled." );
260                try {
261                    InputStream stream = layerList[0].getDataSource()[0].getFeatureInfoURL().openStream();
262                    BufferedReader in = new BufferedReader( new InputStreamReader( stream ) );
263                    StringBuilder sb = new StringBuilder();
264    
265                    while ( in.ready() ) {
266                        sb.append( in.readLine() ).append( "\n" );
267                    }
268    
269                    in.close();
270    
271                    return createGetFeatureInfoResponse( request, null, sb.toString() );
272                } catch ( IOException e ) {
273                    LOG.logError( "Predefined GetFeatureInfo response could not be read/found.", e );
274                }
275            }
276    
277            LinkedList<Callable<Object>> tasks = new LinkedList<Callable<Object>>();
278            for ( int i = 0; i < layerList.length; i++ ) {
279                if ( validate( layerList[i] ) ) {
280                    AbstractDataSource datasource[] = layerList[i].getDataSource();
281                    for ( int j = 0; j < datasource.length; j++ ) {
282                        ScaleHint hint = datasource[j].getScaleHint();
283                        if ( datasource[j].isQueryable() && isValidArea( datasource[j].getValidArea() )
284                             && scale >= hint.getMin() && scale < hint.getMax() ) {
285                            ServiceInvoker si = new ServiceInvoker( layerList[i], datasource[j] );
286                            tasks.add( si );
287                        } else {
288                            LOG.logDebug( "Not using a datasource of layer " + layerList[i].getName()
289                                          + " due to scale or bbox limits." );
290                        }
291                    }
292                } else {
293                    LOG.logDebug( "Layer " + layerList[i].getName() + " not validated." );
294                }
295            }
296    
297            // was: subtract 1 second for architecture overhead and image creation
298            // am not going to do this!
299            long timeLimit = 1000 * ( configuration.getDeegreeParams().getRequestTimeLimit() );
300            timeLimit -= ( runningTime - currentTimeMillis() );
301            try {
302                List<ExecutionFinishedEvent<Object>> list = Executor.getInstance().performSynchronously( tasks, timeLimit );
303                featCol = new Object[list.size()];
304                int i = 0;
305                for ( ExecutionFinishedEvent<Object> evt : list ) {
306                    try {
307                        Object o = evt.getResult();
308                        featCol[i++] = o;
309                    } catch ( CancellationException e ) {
310                        createExceptionResponse( e );
311                    } catch ( Exception e ) {
312                        createExceptionResponse( e );
313                    } catch ( Throwable e ) {
314                        LOG.logError( "Unknown error", e );
315                    }
316                }
317            } catch ( InterruptedException e ) {
318                createExceptionResponse( e );
319            }
320    
321            GetFeatureInfoResult res = createFeatureInfoResponse();
322    
323            return res;
324        }
325    
326        /**
327         * returns true if the requested boundingbox intersects with the valid area of a datasource
328         *
329         * @param validArea
330         */
331        private boolean isValidArea( Geometry validArea ) {
332    
333            if ( validArea != null ) {
334                try {
335                    Envelope env = request.getGetMapRequestCopy().getBoundingBox();
336                    Geometry geom = GeometryFactory.createSurface( env, reqCRS );
337                    if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
338                        // if requested CRS is not identical to the CRS of the valid area
339                        // a transformation must be performed before intersection can
340                        // be checked
341                        GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
342                        geom = gt.transform( geom );
343                    }
344                    return geom.intersects( validArea );
345                } catch ( Exception e ) {
346                    // should never happen
347                    LOG.logError( "Could not validate WMS datasource area", e );
348                }
349            }
350            return true;
351        }
352    
353        /**
354         * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
355         * be thrown. If the layer matches the request, but isn't able to deliver data for the requested area and/or scale
356         * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
357         * true will be returned.
358         *
359         * @param layer
360         *            layer as defined at the capabilities/configuration
361         */
362        private boolean validate( Layer layer )
363                                throws OGCWebServiceException {
364    
365            if ( layer == null ) {
366                return false;
367            }
368    
369            // why the layer can be null here is not known, but just in case:
370            String name = layer.getName();
371    
372            // check for valid coordinated reference system
373            String[] srs = layer.getSrs();
374            boolean tmp = false;
375            for ( int i = 0; i < srs.length; i++ ) {
376                if ( srs[i].equalsIgnoreCase( getMapRequest.getSrs() ) ) {
377                    tmp = true;
378                    break;
379                }
380            }
381    
382            if ( !tmp ) {
383                throw new InvalidSRSException( Messages.getMessage( "WMS_INVALIDSRS", name, getMapRequest.getSrs() ) );
384            }
385    
386            // check bounding box
387            try {
388    
389                Envelope bbox = getMapRequest.getBoundingBox();
390                Envelope layerBbox = layer.getLatLonBoundingBox();
391                if ( !getMapRequest.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
392                    // transform the bounding box of the request to EPSG:4326
393                    GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
394                    bbox = gt.transform( bbox, reqCRS );
395                }
396    
397                if ( !bbox.intersects( layerBbox ) ) {
398                    return false;
399                }
400    
401            } catch ( Exception e ) {
402                throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
403            }
404    
405            return true;
406        }
407    
408        /**
409         * creates a <tt>GetFeatureInfoResult</tt> containing an <tt>OGCWebServiceException</tt>
410         *
411         * @param e
412         *            exception to encapsulate into the response
413         */
414        private GetFeatureInfoResult createExceptionResponse( Exception e ) {
415    
416            OGCWebServiceException exce = null;
417    
418            // default --> application/vnd.ogc.se_xml
419            exce = new OGCWebServiceException( getClass().getName(), e.getMessage() );
420    
421            GetFeatureInfoResult res = WMSProtocolFactory.createGetFeatureInfoResponse( request, exce, null );
422    
423            return res;
424        }
425    
426        /**
427         * generates the desired output from the GMLs
428         *
429         * @return the result object
430         * @throws OGCWebServiceException
431         */
432        private GetFeatureInfoResult createFeatureInfoResponse()
433                                throws OGCWebServiceException {
434    
435            Envelope bbox = getMapRequest.getBoundingBox();
436    
437            StringBuffer sb = new StringBuffer( 20000 );
438            sb.append( "<ll:FeatureCollection " );
439    
440            URL schemaLoc = configuration.getDeegreeParams().getFeatureSchemaLocation();
441            if ( schemaLoc != null ) {
442                sb.append( "xsi:schemaLocation='" );
443                sb.append( configuration.getDeegreeParams().getFeatureSchemaNamespace() );
444                sb.append( " " );
445                sb.append( schemaLoc.toExternalForm() );
446                sb.append( "'" );
447            }
448    
449            sb.append( " xmlns:gml='http://www.opengis.net/gml' " );
450            sb.append( "xmlns:ll='http://www.lat-lon.de' " );
451            sb.append( "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " );
452            URL url = configuration.getDeegreeParams().getSchemaLocation();
453            if ( url != null ) {
454                sb.append( "xsi:schemaLocation='" );
455                sb.append( "http://www.lat-lon.de " + NetWorker.url2String( url ) + "'" );
456            }
457            sb.append( "><gml:boundedBy>" );
458            sb.append( "<gml:Box srsName='" + getMapRequest.getSrs() + "'>" );
459            sb.append( "<gml:coordinates>" + bbox.getMin().getX() + "," );
460            sb.append( bbox.getMin().getY() + " " + bbox.getMax().getX() + "," );
461            sb.append( bbox.getMax().getY() + "</gml:coordinates>" );
462            sb.append( "</gml:Box></gml:boundedBy>" );
463    
464            int cnt = 0;
465    
466            for ( int i = 0; i < featCol.length; i++ ) {
467                if ( featCol[i] instanceof OGCWebServiceException ) {
468                    throw (OGCWebServiceException) featCol[i];
469                }
470                if ( featCol[i] != null ) {
471                    FeatureCollection fc = (FeatureCollection) featCol[i];
472                    cnt = appendFeatureCollection( fc, sb, cnt );
473                }
474    
475                // if ( cnt >= request.getFeatureCount() ) break;
476            }
477            sb.append( "</ll:FeatureCollection>" );
478            LOG.logDebug( "original feature info response: ", sb );
479            GetFeatureInfoResult response = WMSProtocolFactory.createGetFeatureInfoResponse( request, null, sb.toString() );
480    
481            return response;
482        }
483    
484        /**
485         *
486         * @param col
487         * @param sb
488         * @param cnt
489         * @return a counter, probably the same that is given as argument
490         */
491        private int appendFeatureCollection( FeatureCollection col, StringBuffer sb, int cnt ) {
492    
493            Feature[] feat = col.toArray();
494            if ( feat != null ) {
495                for ( int j = 0; j < feat.length; j++ ) {
496                    FeatureType ft = feat[j].getFeatureType();
497                    PropertyType[] ftp = ft.getProperties();
498                    cnt++;
499                    sb.append( "<gml:featureMember>" );
500                    sb.append( "<ll:" ).append( ft.getName().getLocalName() );
501                    sb.append( " fid='" ).append( feat[j].getId().replace( ' ', '_' ) ).append( "'>" );
502                    for ( int i = 0; i < ftp.length; i++ ) {
503                        if ( ftp[i].getType() != Types.GEOMETRY && ftp[i].getType() != Types.POINT
504                             && ftp[i].getType() != Types.CURVE && ftp[i].getType() != Types.SURFACE
505                             && ftp[i].getType() != Types.MULTIPOINT && ftp[i].getType() != Types.MULTICURVE
506                             && ftp[i].getType() != Types.MULTISURFACE ) {
507    
508                            FeatureProperty[] props = feat[j].getProperties( ftp[i].getName() );
509                            if ( props != null ) {
510                                for ( FeatureProperty property : props ) {
511                                    Object value = property.getValue();
512                                    sb.append( "<ll:" + ftp[i].getName().getLocalName() + ">" );
513                                    if ( value instanceof FeatureCollection ) {
514                                        FeatureCollection fc = (FeatureCollection) value;
515                                        appendFeatureCollection( fc, sb, cnt );
516                                    } else {
517                                        sb.append( "<![CDATA[" ).append( value ).append( "]]>" );
518                                    }
519                                    sb.append( "</ll:" + ftp[i].getName().getLocalName() + ">" );
520                                }
521                            }
522                        }
523                    }
524                    sb.append( "</ll:" ).append( ft.getName().getLocalName() ).append( '>' );
525                    sb.append( "</gml:featureMember>" );
526                    if ( cnt >= request.getFeatureCount() )
527                        break;
528                }
529            }
530    
531            return cnt;
532        }
533    
534        // //////////////////////////////////////////////////////////////////////////
535        // inner classes //
536        // //////////////////////////////////////////////////////////////////////////
537    
538        /**
539         * Inner class for accessing the data of one layer and creating a GML document from it. The class extends
540         * <tt>Thread</tt> and implements the run method, so that a parallel data accessing from several layers is possible.
541         *
542         * @version $Revision: 18195 $
543         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
544         */
545        public class ServiceInvoker implements Callable<Object> {
546            private Layer layer = null;
547    
548            private AbstractDataSource datasource = null;
549    
550            /**
551             * Creates a new ServiceInvoker object.
552             *
553             * @param layer
554             * @param datasource
555             */
556            ServiceInvoker( Layer layer, AbstractDataSource datasource ) {
557                this.layer = layer;
558                this.datasource = datasource;
559            }
560    
561            /**
562             * central method for access the data assigned to a data source
563             *
564             * @return result of feature info query
565             */
566            public Object call() {
567                Object response = null;
568                if ( datasource != null ) {
569                    Callable<Object> task = null;
570                    try {
571                        int type = datasource.getType();
572                        switch ( type ) {
573                        case AbstractDataSource.LOCALWFS:
574                        case AbstractDataSource.REMOTEWFS: {
575                            OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource );
576                            task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
577                            break;
578                        }
579                        case AbstractDataSource.LOCALWCS:
580                        case AbstractDataSource.REMOTEWCS: {
581                            OGCWebServiceRequest request = GetMapServiceInvokerForNL.createGetCoverageRequest( datasource,
582                                                                                                               getMapRequest );
583                            task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
584                            break;
585                        }
586                        case AbstractDataSource.REMOTEWMS: {
587                            OGCWebServiceRequest request = createGetFeatureInfo( datasource );
588                            task = new DoServiceTask<Object>( datasource.getOGCWebService(), request );
589                            break;
590                        }
591                        case AbstractDataSource.DATABASE: {
592                            DatabaseDataSource dbds = (DatabaseDataSource) datasource;
593                            Envelope target = calcTargetArea( dbds, dbds.getNativeCRS().getPrefixedName() );
594    
595                            for ( Dimension dim : configuration.getLayer( layer.getName() ).getDimension() ) {
596                                if ( dim.getDefaultValue() != null ) {
597                                    if ( dim.getName().equals( "time" )
598                                         && request.getGetMapRequestCopy().getDimTime() == null ) {
599                                        request.getGetMapRequestCopy().setDimTime(
600                                                                                   new DimensionValues(
601                                                                                                        dim.getDefaultValue() ) );
602                                    } else if ( dim.getName().equals( "elevation" )
603                                                && request.getGetMapRequestCopy().getDimElev() == null ) {
604                                        request.getGetMapRequestCopy().setDimElev(
605                                                                                   new DimensionValues(
606                                                                                                        dim.getDefaultValue() ) );
607                                    }
608                                }
609                            }
610    
611                            task = new DoDatabaseQueryTask( (DatabaseDataSource) datasource, target, null,
612                                                            datasource.getDimProps(), request.getGetMapRequestCopy() );
613                            break;
614                        }
615                        case AbstractDataSource.EXTERNALDATAACCESS: {
616                            ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess();
617                            task = new DoExternalAccessTask( eda, request );
618                            break;
619                        }
620                        }
621                    } catch ( Exception e ) {
622                        OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvoker: " + layer.getName(),
623                                                                                  Messages.getMessage( "WMS_CREATE_QUERY" ) );
624                        response = exce;
625                        LOG.logError( Messages.getMessage( "WMS_CREATE_QUERY" ) + ": " + e.getMessage(), e );
626                        throw new RuntimeException( e );
627                    }
628    
629                    try {
630                        Executor executor = Executor.getInstance();
631                        Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 );
632                        response = handleResponse( o );
633                    } catch ( CancellationException e ) {
634                        // exception can't be re-thrown because responsible GetMapHandler
635                        // must collect all responses of all data sources
636                        String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE",
637                                                        new Integer( datasource.getRequestTimeLimit() ) );
638                        LOG.logError( s, e );
639                        if ( datasource.isFailOnException() ) {
640                            OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
641                            response = exce;
642                        } else {
643                            response = null;
644                        }
645                    } catch ( Throwable t ) {
646                        // exception can't be re-thrown because responsible GetMapHandler
647                        // must collect all responses of all data sources
648                        String s = Messages.getMessage( "WMS_ERRORDOSERVICE", t.getMessage() );
649                        LOG.logError( s, t );
650                        if ( datasource.isFailOnException() ) {
651                            OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
652                            response = exce;
653                        } else {
654                            response = null;
655                        }
656                    }
657    
658                }
659    
660                return response;
661    
662            }
663    
664            /**
665             * creates a getFeature request considering the getMap request and the filter conditions defined in the
666             * submitted <tt>DataSource</tt> object. The request will be encapsulated within a <tt>OGCWebServiceEvent</tt>.
667             *
668             * @param ds
669             * @return GetFeature request object
670             */
671            private GetFeature createGetFeatureRequest( LocalWFSDataSource ds )
672                                    throws Exception {
673    
674                WFSCapabilities capa;
675                if ( ds.getOGCWebService() instanceof WFService ) {
676                    WFService se = (WFService) ds.getOGCWebService();
677                    capa = se.getCapabilities();
678                } else {
679                    RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService();
680                    capa = wfs.getWFSCapabilities();
681                }
682                QualifiedName gn = ds.getName();
683                WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
684    
685                if ( ft == null ) {
686                    throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) );
687                }
688                String crs = ft.getDefaultSRS().toASCIIString();
689                Envelope targetArea = calcTargetArea( ds, crs );
690    
691                // no filter condition has been defined
692                StringBuffer sb = new StringBuffer( 2000 );
693                sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
694                sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " );
695                sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
696                sb.append( "xmlns:wfs='http://www.opengis.net/wfs' " );
697                sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
698                sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' );
699                sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " );
700                sb.append( "service='WFS' version='1.1.0' " );
701                if ( ds.getType() == AbstractDataSource.LOCALWFS ) {
702                    sb.append( "outputFormat='FEATURECOLLECTION'>" );
703                } else {
704                    sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
705                }
706                sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" );
707    
708                Query query = ds.getQuery();
709    
710                // append <wfs:PropertyName> elements
711                if ( query != null && query.getPropertyNames() != null ) {
712                    PropertyPath[] propertyNames = query.getPropertyNames();
713                    for ( PropertyPath path : propertyNames ) {
714                        NamespaceContext nsContext = path.getNamespaceContext();
715                        sb.append( "<wfs:PropertyName" );
716                        Map<String, URI> namespaceMap = nsContext.getNamespaceMap();
717                        Iterator<String> prefixIter = namespaceMap.keySet().iterator();
718                        while ( prefixIter.hasNext() ) {
719                            String prefix = prefixIter.next();
720                            if ( !CommonNamespaces.XMLNS_PREFIX.equals( prefix ) ) {
721                                URI namespace = namespaceMap.get( prefix );
722                                sb.append( " xmlns:" + prefix + "=\"" + namespace + "\"" );
723                            }
724                        }
725                        sb.append( '>' );
726                        sb.append( path.getAsString() );
727                        sb.append( "</wfs:PropertyName>" );
728                    }
729                }
730    
731                sb.append( "<ogc:Filter>" );
732                if ( query == null ) {
733                    // BBOX operation for speeding up the search at simple datasources
734                    // like shapes
735                    sb.append( "<ogc:BBOX><PropertyName>" );
736                    sb.append( ds.getGeometryProperty().getPrefixedName() );
737                    sb.append( "</PropertyName>" );
738                    sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) );
739                    sb.append( "</ogc:BBOX>" );
740                    sb.append( "</ogc:Filter></Query></GetFeature>" );
741                } else {
742                    Filter filter = query.getFilter();
743    
744                    sb.append( "<ogc:And>" );
745                    // BBOX operation for speeding up the search at simple datasources
746                    // like shapes
747                    sb.append( "<ogc:BBOX><PropertyName>" + ds.getGeometryProperty().getPrefixedName() );
748                    sb.append( "</PropertyName>" );
749                    sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) );
750                    sb.append( "</ogc:BBOX>" );
751    
752                    if ( filter instanceof ComplexFilter ) {
753                        org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation();
754                        sb.append( op.toXML() );
755                    } else {
756                        ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
757                        for ( int i = 0; i < featureIds.size(); i++ ) {
758                            FeatureId fid = featureIds.get( i );
759                            sb.append( fid.toXML() );
760                        }
761                    }
762                    sb.append( "</ogc:And></ogc:Filter></Query></GetFeature>" );
763                }
764    
765                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
766                    LOG.logDebug( "GetFeature-request:\n" + sb );
767                }
768    
769                // create dom representation of the request
770                StringReader sr = new StringReader( sb.toString() );
771                Document doc = XMLTools.parse( sr );
772    
773                // create OGCWebServiceEvent object
774                IDGenerator idg = IDGenerator.getInstance();
775                GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
776    
777                return gfr;
778            }
779    
780            /**
781             * calculates the target area for the getfeatureinfo request from the maps bounding box, the its size and the
782             * image coordinates of interest. An area is calculated instead of using a point because to consider
783             * uncertainties determining the point of interest
784             *
785             * @param ds
786             *            <tt>DataSource</tt> of the layer that is requested for feature infos (each layer may be offered in
787             *            its own crs)
788             * @param crs
789             */
790            private Envelope calcTargetArea( AbstractDataSource ds, String crs )
791                                    throws OGCWebServiceException {
792    
793                int width = request.getGetMapRequestCopy().getWidth();
794                int height = request.getGetMapRequestCopy().getHeight();
795                int x = request.getClickPoint().x;
796                int y = request.getClickPoint().y;
797    
798                Envelope bbox = request.getGetMapRequestCopy().getBoundingBox();
799    
800                // transform request bounding box to the coordinate reference
801                // system the WFS holds the data if requesting CRS and WFS-Data
802                // crs are different
803                Envelope tBbox = null;
804                try {
805                    GeoTransform gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(),
806                                                                  bbox.getMax().getX(), bbox.getMax().getY(), 0, 0,
807                                                                  width - 1, height - 1 );
808                    double[] target = new double[4];
809                    int rad = configuration.getDeegreeParams().getFeatureInfoRadius();
810                    target[0] = gt.getSourceX( x - rad );
811                    target[1] = gt.getSourceY( y + rad );
812                    target[2] = gt.getSourceX( x + rad );
813                    target[3] = gt.getSourceY( y - rad );
814    
815                    tBbox = GeometryFactory.createEnvelope( target[0], target[1], target[2], target[3], null );
816                    if ( !( crs.equalsIgnoreCase( request.getGetMapRequestCopy().getSrs() ) ) ) {
817                        GeoTransformer transformer = new GeoTransformer( CRSFactory.create( crs ) );
818                        tBbox = transformer.transform( tBbox, reqCRS );
819                    }
820    
821                } catch ( Exception e ) {
822                    throw new OGCWebServiceException( e.toString() );
823                }
824    
825                return tBbox;
826            }
827    
828            /**
829             * creates a GetFeatureInfo request for requesting a cascaded remote WMS The request will be encapsulated within
830             * a <tt>OGCWebServiceEvent</tt>.
831             *
832             * @param ds
833             * @return GetFeatureInfo request object
834             */
835            private GetFeatureInfo createGetFeatureInfo( AbstractDataSource ds ) {
836    
837                // create embbeded map request
838                GetMap gmr = ( (RemoteWMSDataSource) ds ).getGetMapRequest();
839    
840                String format = getMapRequest.getFormat();
841    
842                if ( gmr != null && !"%default%".equals( gmr.getFormat() ) ) {
843                    format = gmr.getFormat();
844                }
845    
846                org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] lys = null;
847                lys = new org.deegree.ogcwebservices.wms.operation.GetMap.Layer[1];
848                lys[0] = GetMap.createLayer( layer.getName(), "$DEFAULT" );
849    
850                if ( gmr != null && gmr.getLayers() != null ) {
851                    lys = gmr.getLayers();
852                }
853                Color bgColor = getMapRequest.getBGColor();
854                if ( gmr != null && gmr.getBGColor() != null ) {
855                    bgColor = gmr.getBGColor();
856                }
857                Values time = getMapRequest.getTime();
858                if ( gmr != null && gmr.getTime() != null ) {
859                    time = gmr.getTime();
860                }
861                Map<String, String> vendorSpecificParameter = getMapRequest.getVendorSpecificParameters();
862                if ( gmr != null && gmr.getVendorSpecificParameters() != null
863                     && gmr.getVendorSpecificParameters().size() > 0 ) {
864                    vendorSpecificParameter = gmr.getVendorSpecificParameters();
865                }
866                String version = "1.1.0";
867                if ( gmr != null && gmr.getVersion() != null ) {
868                    version = gmr.getFormat();
869                }
870                Values elevation = getMapRequest.getElevation();
871                if ( gmr != null && gmr.getElevation() != null ) {
872                    elevation = gmr.getElevation();
873                }
874                Map<String, Values> sampleDim = null;
875                if ( gmr != null && gmr.getSampleDimension() != null ) {
876                    sampleDim = gmr.getSampleDimension();
877                }
878    
879                IDGenerator idg = IDGenerator.getInstance();
880                gmr = GetMap.create( version, "" + idg.generateUniqueID(), lys, elevation, sampleDim, format,
881                                     getMapRequest.getWidth(), getMapRequest.getHeight(), getMapRequest.getSrs(),
882                                     getMapRequest.getBoundingBox(), getMapRequest.getTransparency(), bgColor,
883                                     getMapRequest.getExceptions(), time, null, null, vendorSpecificParameter );
884    
885                // create GetFeatureInfo request for cascaded/remote WMS
886                String[] queryLayers = new String[] { ds.getName().getPrefixedName() };
887                GetFeatureInfo req = GetFeatureInfo.create( "1.1.0", this.toString(), queryLayers, gmr,
888                                                            "application/vnd.ogc.gml", request.getFeatureCount(),
889                                                            request.getClickPoint(), request.getExceptions(), null,
890                                                            request.getVendorSpecificParameters() );
891    
892                try {
893                    LOG.logDebug( "cascaded GetFeatureInfo request: ", req.getRequestParameter() );
894                } catch ( OGCWebServiceException e ) {
895                    LOG.logError( e.getMessage(), e );
896                }
897    
898                return req;
899            }
900    
901            /**
902             * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS implementation accessed by
903             * this class is able to return the result of a request by calling the write-method.
904             *
905             * @param result
906             *            to a GetXXX request
907             * @return the response object
908             * @throws Exception
909             */
910            private Object handleResponse( Object result )
911                                    throws Exception {
912                Object response = null;
913                if ( result instanceof FeatureResult ) {
914                    response = handleGetFeatureResponse( (FeatureResult) result );
915                } else if ( result instanceof ResultCoverage ) {
916                    response = handleGetCoverageResponse( (ResultCoverage) result );
917                } else if ( result instanceof GetFeatureInfoResult ) {
918                    response = handleGetFeatureInfoResult( (GetFeatureInfoResult) result );
919                } else {
920                    throw new Exception( Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ) );
921                }
922                return response;
923            }
924    
925            /**
926             * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from
927             * it
928             *
929             * @param response
930             * @return the response objects
931             * @throws Exception
932             */
933            private Object handleGetFeatureResponse( FeatureResult response )
934                                    throws Exception {
935                FeatureCollection fc = null;
936    
937                Object o = response.getResponse();
938                if ( o instanceof FeatureCollection ) {
939                    fc = (FeatureCollection) o;
940                } else if ( o.getClass() == byte[].class ) {
941                    Reader reader = new InputStreamReader( new ByteArrayInputStream( (byte[]) o ) );
942                    GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
943                    doc.load( reader, XMLFragment.DEFAULT_URL );
944                    fc = doc.parse();
945                } else {
946                    throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) );
947                }
948                return fc;
949            }
950    
951            /**
952             *
953             * @param res
954             */
955            private Object handleGetFeatureInfoResult( GetFeatureInfoResult res )
956                                    throws Exception {
957    
958                FeatureCollection fc = null;
959                StringReader sr = new StringReader( res.getFeatureInfo() );
960                XMLFragment xml = new XMLFragment( sr, XMLFragment.DEFAULT_URL );
961    
962                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
963                    LOG.logDebug( "GetFeature-response (before transformation): " + xml.getAsPrettyString() );
964                }
965    
966                if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) {
967                    URL url = ( (RemoteWMSDataSource) datasource ).getFeatureInfoTransform();
968                    if ( url != null ) {
969                        // transform incoming GML/XML to a GML application schema
970                        // that is understood by deegree
971                        XSLTDocument xslt = new XSLTDocument();
972                        xslt.load( url );
973                        xml = xslt.transform( xml, null, null, null );
974                    }
975                }
976                GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
977                doc.setRootElement( xml.getRootElement() );
978                fc = doc.parse();
979                return fc;
980            }
981    
982            private Object handleGetCoverageResponse( ResultCoverage response )
983                                    throws OGCWebServiceException {
984                ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
985                if ( gc != null ) {
986                    BufferedImage bi = gc.getAsImage( -1, -1 );
987    
988                    float[][] data = new Image2RawData( bi ).parse();
989    
990                    double scaleX = (double) data[0].length / (double) getMapRequest.getWidth();
991                    double scaleY = (double) data.length / (double) getMapRequest.getHeight();
992                    int pxSizeX = (int) Math.round( scaleX );
993                    int pxSizeY = (int) Math.round( scaleY );
994                    if ( pxSizeX == 0 ) {
995                        pxSizeX = 1;
996                    }
997                    if ( pxSizeY == 0 ) {
998                        pxSizeY = 1;
999                    }
1000    
1001                    LOG.logDebug( "Size of grid coverage is " + data[0].length + "x" + data.length + "." );
1002                    LOG.logDebug( "Returning an area of " + pxSizeX + "x" + pxSizeY + " pixels." );
1003    
1004                    int ix = (int) ( request.getClickPoint().x * scaleX ) - pxSizeX / 2;
1005                    int iy = (int) ( request.getClickPoint().y * scaleY ) - pxSizeY / 2;
1006    
1007                    // some checks to avoid areas that are not requestable
1008                    if ( ix < 0 ) {
1009                        ix = 0;
1010                    }
1011                    if ( iy < 0 ) {
1012                        iy = 0;
1013                    }
1014                    if ( ix >= ( data[0].length - pxSizeX ) ) {
1015                        ix = data[0].length - pxSizeX - 1;
1016                    }
1017                    if ( iy >= ( data.length - pxSizeY ) ) {
1018                        iy = data.length - pxSizeY - 1;
1019                    }
1020    
1021                    FeatureCollection fc = FeatureFactory.createFeatureCollection( gc.getCoverageOffering().getName(),
1022                                                                                   pxSizeX * pxSizeY );
1023    
1024                    PropertyType pt = null;
1025                    try {
1026                        pt = FeatureFactory.createPropertyType( VALUE, new QualifiedName( "xsd", "double",
1027                                                                                          CommonNamespaces.XSNS ), 1, 1 );
1028                    } catch ( UnknownTypeException e ) {
1029                        LOG.logError( "The xsd:double type is not known?!? Get a new deegree.jar!", e );
1030                    }
1031                    FeatureType ft = FeatureFactory.createFeatureType( gc.getCoverageOffering().getName(), false,
1032                                                                       new PropertyType[] { pt } );
1033    
1034                    for ( int x = ix; x < ix + pxSizeX; ++x ) {
1035                        for ( int y = iy; y < iy + pxSizeY; ++y ) {
1036                            FeatureProperty p = FeatureFactory.createFeatureProperty( VALUE, new Double( data[y][x] ) );
1037                            Feature f = FeatureFactory.createFeature( "ID_faked_for_" + x + "x" + y, ft,
1038                                                                      new FeatureProperty[] { p } );
1039                            fc.add( f );
1040                        }
1041                    }
1042    
1043                    return fc;
1044                }
1045    
1046                throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE",
1047                                                                                             datasource.getName() ) );
1048    
1049            }
1050    
1051        }
1052    
1053    }