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