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