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