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