001    //$HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/src/org/deegree/ogcwebservices/wms/GetMapServiceInvokerForNL.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 org.deegree.graphics.sld.StyleUtils.extractRequiredProperties;
039    
040    import static org.deegree.i18n.Messages.get;
041    import static org.deegree.model.spatialschema.GMLGeometryAdapter.exportAsEnvelope;
042    import static org.deegree.ogcbase.ExceptionCode.INVALIDDIMENSIONVALUE;
043    import static org.deegree.ogcbase.ExceptionCode.MISSINGDIMENSIONVALUE;
044    import static org.deegree.ogcbase.PropertyPathFactory.createPropertyPath;
045    
046    import java.awt.Color;
047    import java.awt.Graphics;
048    import java.awt.image.BufferedImage;
049    import java.io.StringReader;
050    import java.net.URI;
051    import java.util.ArrayList;
052    import java.util.Iterator;
053    import java.util.LinkedList;
054    import java.util.List;
055    import java.util.Map;
056    import java.util.concurrent.Callable;
057    import java.util.concurrent.CancellationException;
058    
059    import org.deegree.datatypes.QualifiedName;
060    import org.deegree.datatypes.Types;
061    import org.deegree.framework.concurrent.DoDatabaseQueryTask;
062    import org.deegree.framework.concurrent.DoExternalAccessTask;
063    import org.deegree.framework.concurrent.DoServiceTask;
064    import org.deegree.framework.concurrent.Executor;
065    import org.deegree.framework.log.ILogger;
066    import org.deegree.framework.log.LoggerFactory;
067    import org.deegree.framework.util.CharsetUtils;
068    import org.deegree.framework.util.IDGenerator;
069    import org.deegree.framework.xml.XMLFragment;
070    import org.deegree.framework.xml.XMLTools;
071    import org.deegree.graphics.MapFactory;
072    import org.deegree.graphics.Theme;
073    import org.deegree.graphics.sld.FeatureTypeConstraint;
074    import org.deegree.graphics.sld.LayerFeatureConstraints;
075    import org.deegree.graphics.sld.NamedLayer;
076    import org.deegree.graphics.sld.UserStyle;
077    import org.deegree.i18n.Messages;
078    import org.deegree.model.coverage.grid.GridCoverage;
079    import org.deegree.model.coverage.grid.ImageGridCoverage;
080    import org.deegree.model.coverage.grid.WorldFile;
081    import org.deegree.model.crs.CRSFactory;
082    import org.deegree.model.crs.CRSTransformationException;
083    import org.deegree.model.crs.CoordinateSystem;
084    import org.deegree.model.crs.GeoTransformer;
085    import org.deegree.model.crs.UnknownCRSException;
086    import org.deegree.model.feature.Feature;
087    import org.deegree.model.feature.FeatureCollection;
088    import org.deegree.model.feature.FeatureProperty;
089    import org.deegree.model.feature.schema.FeatureType;
090    import org.deegree.model.feature.schema.PropertyType;
091    import org.deegree.model.filterencoding.ComplexFilter;
092    import org.deegree.model.filterencoding.FeatureFilter;
093    import org.deegree.model.filterencoding.FeatureId;
094    import org.deegree.model.filterencoding.Filter;
095    import org.deegree.model.spatialschema.Envelope;
096    import org.deegree.model.spatialschema.Geometry;
097    import org.deegree.model.spatialschema.GeometryException;
098    import org.deegree.model.spatialschema.GeometryFactory;
099    import org.deegree.model.spatialschema.WKTAdapter;
100    import org.deegree.ogcbase.PropertyPath;
101    import org.deegree.ogcbase.PropertyPathFactory;
102    import org.deegree.ogcbase.SortProperty;
103    import org.deegree.ogcwebservices.InconsistentRequestException;
104    import org.deegree.ogcwebservices.InvalidParameterValueException;
105    import org.deegree.ogcwebservices.OGCWebService;
106    import org.deegree.ogcwebservices.OGCWebServiceException;
107    import org.deegree.ogcwebservices.OGCWebServiceRequest;
108    import org.deegree.ogcwebservices.wcs.WCSException;
109    import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage;
110    import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
111    import org.deegree.ogcwebservices.wfs.RemoteWFService;
112    import org.deegree.ogcwebservices.wfs.WFService;
113    import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities;
114    import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType;
115    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
116    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
117    import org.deegree.ogcwebservices.wfs.operation.Query;
118    import org.deegree.ogcwebservices.wms.capabilities.Dimension;
119    import org.deegree.ogcwebservices.wms.capabilities.Layer;
120    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
121    import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
122    import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource;
123    import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource;
124    import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource;
125    import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource;
126    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
127    import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess;
128    import org.deegree.ogcwebservices.wms.operation.DimensionValues;
129    import org.deegree.ogcwebservices.wms.operation.GetMap;
130    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
131    import org.deegree.ogcwebservices.wms.operation.DimensionValues.DimensionValue;
132    import org.w3c.dom.Document;
133    
134    /**
135     * Class for accessing the data of one layers datasource and creating a <tt>Theme</tt> from it.
136     * 
137     * @version $Revision: 32291 $
138     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
139     * @author last edited by: $Author: apoth $
140     * 
141     * @version 1.0. $Revision: 32291 $, $Date: 2011-10-24 16:59:08 +0200 (Mo, 24 Okt 2011) $
142     * 
143     * @since 2.0
144     */
145    public class GetMapServiceInvokerForNL extends GetMapServiceInvoker implements Callable<Object> {
146    
147        private static final ILogger LOG = LoggerFactory.getLogger( GetMapServiceInvokerForNL.class );
148    
149        private final GetMap request;
150    
151        private NamedLayer layer = null;
152    
153        private UserStyle style = null;
154    
155        private AbstractDataSource datasource = null;
156    
157        /**
158         * Creates a new ServiceInvokerForNL object.
159         * 
160         * @param handler
161         * @param layer
162         * @param datasource
163         * @param style
164         * @param scale
165         */
166        GetMapServiceInvokerForNL( DefaultGetMapHandler handler, NamedLayer layer, AbstractDataSource datasource,
167                                   UserStyle style, double scale ) {
168    
169            super( handler, scale );
170    
171            this.layer = layer;
172            this.request = handler.getRequest();
173            this.style = style;
174            this.datasource = datasource;
175        }
176    
177        /**
178         * @return the callable actually fetching the data
179         * @throws Exception
180         */
181        public Callable<Object> getWrappedCallable()
182                                throws Exception {
183            int type = datasource.getType();
184            switch ( type ) {
185            case AbstractDataSource.LOCALWFS:
186            case AbstractDataSource.REMOTEWFS: {
187                OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource );
188                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
189            }
190            case AbstractDataSource.LOCALWCS:
191            case AbstractDataSource.REMOTEWCS: {
192                OGCWebServiceRequest request = createGetCoverageRequest( datasource, this.request );
193                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
194            }
195            case AbstractDataSource.REMOTEWMS: {
196                String styleName = null;
197    
198                if ( style != null ) {
199                    styleName = style.getName();
200                }
201    
202                OGCWebServiceRequest request = GetMap.createGetMapRequest( datasource, handler.getRequest(), styleName,
203                                                                           layer.getName() );
204                LOG.logDebug( "GetMap request: " + request.toString() );
205                return new DoServiceTask<Object>( datasource.getOGCWebService(), request );
206            }
207            case AbstractDataSource.DATABASE: {
208                CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
209                Envelope env = this.request.getBoundingBox();
210                env = GeometryFactory.createEnvelope( env.getMin(), env.getMax(), crs );
211    
212                // set dim default values TODO do so in general?
213                for ( Dimension dim : handler.getConfiguration().getLayer( layer.getName() ).getDimension() ) {
214                    if ( dim.getDefaultValue() != null ) {
215                        if ( dim.getName().equals( "time" ) && request.getDimTime() == null ) {
216                            request.setDimTime( new DimensionValues( dim.getDefaultValue() ) );
217                        } else if ( dim.getName().equals( "elevation" ) && request.getDimElev() == null ) {
218                            request.setDimElev( new DimensionValues( dim.getDefaultValue() ) );
219                        }
220                    }
221                }
222    
223                if ( handler.sqls != null ) {
224                    return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env,
225                                                    handler.sqls.get( layer.getName() ), datasource.getDimProps(), request );
226                }
227                return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env, null, datasource.getDimProps(),
228                                                request );
229            }
230            case AbstractDataSource.EXTERNALDATAACCESS: {
231                ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess();
232                return new DoExternalAccessTask( eda, request );
233            }
234            default:
235                return null;
236            }
237        }
238    
239        public Object call()
240                                throws OGCWebServiceException {
241    
242            Object response = null;
243            if ( datasource != null ) {
244                Callable<Object> task = null;
245                try {
246                    task = getWrappedCallable();
247                } catch ( OGCWebServiceException e ) {
248                    throw e;
249                } catch ( Exception e ) {
250                    LOG.logError( "Exception during fetching data for some data source", e );
251                    OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(),
252                                                                              Messages.getMessage( "WMS_ERRORQUERYCREATE",
253                                                                                                   e ) );
254                    // exception can't be re-thrown because responsible GetMapHandler
255                    // must collect all responses of all data sources
256                    response = exce;
257                }
258    
259                try {
260                    // start reading data with a limited time frame. The time limit
261                    // read from the data source must be multiplied by 1000 because
262                    // the method expects milliseconds as time limit
263                    Executor executor = Executor.getInstance();
264                    Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 );
265                    response = handleResponse( o );
266                } catch ( CancellationException e ) {
267                    // exception can't be re-thrown because responsible GetMapHandler
268                    // must collect all responses of all data sources
269                    String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE", new Integer( datasource.getRequestTimeLimit() ) );
270                    LOG.logError( s, e );
271                    if ( datasource.isFailOnException() ) {
272                        OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
273                        response = exce;
274                    } else {
275                        response = null;
276                    }
277                } catch ( WMSExceptionFromWCS e ) {
278                    if ( datasource.isFailOnException() ) {
279                        response = e;
280                    }
281                } catch ( Throwable e ) {
282                    // exception can't be re-thrown because responsible GetMapHandler
283                    // must collect all responses of all data sources
284                    String s = Messages.getMessage( "WMS_ERRORDOSERVICE", e.getMessage() );
285                    LOG.logError( s, e );
286                    if ( datasource.isFailOnException() ) {
287                        OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
288                        response = exce;
289                    } else {
290                        response = null;
291                    }
292                }
293            }
294    
295            LOG.logDebug( "Layer " + layer.getName() + " returned." );
296    
297            return response;
298        }
299    
300        /**
301         * creates a getFeature request considering the getMap request and the filterconditions defined in the submitted
302         * <tt>DataSource</tt> object. The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>.
303         * 
304         * @param ds
305         * @return GetFeature request object
306         * @throws Exception
307         */
308        private GetFeature createGetFeatureRequest( LocalWFSDataSource ds )
309                                throws Exception {
310    
311            Envelope bbox = transformBBOX( ds );
312    
313            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(), sortProperties = new LinkedList<StringBuffer>();
314    
315            List<PropertyPath> pp = null;
316            if ( style != null ) {
317                List<UserStyle> styleList = new ArrayList<UserStyle>();
318                styleList.add( style );
319                pp = extractRequiredProperties( ds.getName(), styleList, scaleDen );
320            } else {
321                pp = new ArrayList<PropertyPath>();
322            }
323            PropertyPath geomPP = PropertyPathFactory.createPropertyPath( ds.getGeometryProperty() );
324            if ( !pp.contains( geomPP ) ) {
325                pp.add( geomPP );
326            }
327    
328            // handling of vendor specific parameters "filter property" and "filter value"
329            String filterProperty = request.getVendorSpecificParameter( "FILTERPROPERTY" );
330            String filterValue = request.getVendorSpecificParameter( "FILTERVALUE" );
331    
332            boolean useCustomFilter = handler.getConfiguration().getDeegreeParams().getFiltersAllowed();
333            useCustomFilter = useCustomFilter && filterProperty != null && filterValue != null;
334    
335            if ( useCustomFilter ) {
336                LOG.logDebug( "Using custom filter on " + filterProperty + " = " + filterValue );
337                PropertyPath path = createPropertyPath( new QualifiedName( filterProperty ) );
338                if ( !pp.contains( path ) ) {
339                    pp.add( path );
340                }
341            }
342    
343            LOG.logDebug( "required properties: ", pp );
344            Map<String, URI> namesp = extractNameSpaceDef( pp );
345    
346            // no filter condition has been defined
347            StringBuffer sb = new StringBuffer( 5000 );
348            sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
349            sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " );
350            sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
351            sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
352            sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' );
353            sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " );
354            Iterator<String> iter = namesp.keySet().iterator();
355            while ( iter.hasNext() ) {
356                String pre = iter.next();
357                URI nsp = namesp.get( pre );
358                if ( !pre.equals( "xmlns" ) && !pre.equals( ds.getName().getPrefix() ) ) {
359                    sb.append( "xmlns:" ).append( pre ).append( "='" );
360                    sb.append( nsp.toASCIIString() ).append( "' " );
361                }
362            }
363    
364            sb.append( "service='WFS' version='1.1.0' " );
365            if ( ds.getType() == AbstractDataSource.LOCALWFS ) {
366                sb.append( "outputFormat='FEATURECOLLECTION'>" );
367            } else {
368                sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
369            }
370            sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" );
371    
372            // please note that this code CANNOT BE UNCOMMENTED as it severely affects performance in many cases!
373            for ( int j = 0; j < pp.size(); j++ ) {
374                if ( !pp.get( j ).getAsString().endsWith( "$SCALE" ) ) {
375                    sb.append( "<PropertyName>" ).append( pp.get( j ).getAsString() );
376                    sb.append( "</PropertyName>" );
377                }
378            }
379    
380            // add filters to list
381            // BBOX filter
382            StringBuffer sb2 = new StringBuffer( 512 );
383            sb2.append( "<ogc:BBOX>" );
384            sb2.append( "<ogc:PropertyName>" );
385            sb2.append( ds.getGeometryProperty().getPrefixedName() );
386            sb2.append( "</ogc:PropertyName>" );
387            sb2.append( exportAsEnvelope( bbox ) );
388            sb2.append( "</ogc:BBOX>" );
389            filters.add( sb2 );
390            // custom filter for property value
391            if ( useCustomFilter ) {
392                sb2 = new StringBuffer( 512 );
393                appendCustomFilter( sb2, filterProperty, filterValue );
394                filters.add( sb2 );
395            }
396            // filters of query
397            Query query = ds.getQuery();
398            if ( query != null ) {
399                Filter filter = query.getFilter();
400                filters.addAll( extractFilters( filter ) );
401                SortProperty[] sps = query.getSortProperties();
402                if ( sps != null ) {
403                    for ( SortProperty sp : sps ) {
404                        sortProperties.add( extractSortProperty( sp ) );
405                    }
406                }
407            }
408            // filters from SLD
409            if ( layer != null ) {
410                LayerFeatureConstraints lfc = layer.getLayerFeatureConstraints();
411                if ( lfc != null ) {
412                    FeatureTypeConstraint[] fcs = lfc.getFeatureTypeConstraint();
413                    if ( fcs != null ) {
414                        for ( FeatureTypeConstraint fc : fcs ) {
415                            filters.addAll( extractFilters( fc.getFilter() ) );
416                        }
417                    }
418                }
419            }
420    
421            // filters from the dimensions
422            Layer lay = handler.getConfiguration().getLayer( layer.getName() );
423            Map<String, String> dimProps = ds.getDimProps();
424            if ( lay.getDimension() != null && dimProps != null ) {
425                for ( Dimension dim : lay.getDimension() ) {
426                    if ( dim.getName().equals( "time" ) ) {
427                        filters.add( handleDimension( request.getDimTime(), dim, dimProps.get( "time" ) ) );
428                    }
429                    if ( dim.getName().equals( "elevation" ) ) {
430                        filters.add( handleDimension( request.getDimElev(), dim, dimProps.get( "elevation" ) ) );
431                    }
432                }
433            }
434    
435            // actually append the filters
436            sb.append( "<ogc:Filter>" );
437            if ( filters.size() > 1 ) {
438                sb.append( "<ogc:And>" );
439            }
440            for ( StringBuffer s : filters ) {
441                sb.append( s );
442            }
443            if ( filters.size() > 1 ) {
444                sb.append( "</ogc:And>" );
445            }
446            sb.append( "</ogc:Filter>" );
447            if ( sortProperties.size() > 0 ) {
448                sb.append( "<ogc:SortBy>" );
449                for ( StringBuffer s : sortProperties ) {
450                    sb.append( s );
451                }
452                sb.append( "</ogc:SortBy>" );
453            }
454            sb.append( "</Query></GetFeature>" );
455    
456            // create dom representation of the request
457            Document doc = XMLTools.parse( new StringReader( sb.toString() ) );
458    
459            if ( LOG.isDebug() ) {
460                LOG.logDebug( "GetFeature request: "
461                              + new XMLFragment( doc, "http://www.systemid.org" ).getAsPrettyString() );
462            }
463    
464            // create OGCWebServiceEvent object
465            IDGenerator idg = IDGenerator.getInstance();
466            GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() );
467    
468            return gfr;
469        }
470    
471        private StringBuffer handleDimension( DimensionValues values, Dimension dim, String dimProp )
472                                throws OGCWebServiceException {
473            if ( values == null && dim.getDefaultValue() != null ) {
474                values = new DimensionValues( dim.getDefaultValue() );
475                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" + dim.getDefaultValue() + " "
476                                            + dim.getUnits() );
477            }
478    
479            if ( values == null ) {
480                throw new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", dim.getName() ),
481                                                          MISSINGDIMENSIONVALUE );
482            }
483            if ( !dim.isMultipleValues() && values.hasMultipleValues() ) {
484                throw new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", dim.getName() ),
485                                                          INVALIDDIMENSIONVALUE );
486            }
487    
488            String singleValue = values.values.peek().value;
489            if ( values.values.size() == 1 && singleValue != null ) {
490                DimensionValues origValues = new DimensionValues( dim.getValues() );
491                if ( !origValues.includesValue( singleValue ) ) {
492                    if ( dim.isNearestValue() ) {
493                        String nearestValue = origValues.getNearestValue( singleValue );
494                        values.values.peek().value = nearestValue;
495                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" + nearestValue + " "
496                                                    + dim.getUnits() );
497                    }
498                }
499            }
500    
501            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
502    
503            for ( DimensionValue val : values.values ) {
504                StringBuffer sb = new StringBuffer( 512 );
505                if ( val.value != null ) {
506                    sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ).append( dimProp );
507                    sb.append( "</ogc:PropertyName><ogc:Literal>" ).append( val.value );
508                    sb.append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
509                } else {
510                    sb.append( "<ogc:PropertyIsBetween><ogc:PropertyName>" ).append( dimProp );
511                    sb.append( "</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>" ).append( val.low );
512                    sb.append( "</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>" ).append( val.high );
513                    sb.append( "</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween>" );
514                }
515                filters.add( sb );
516            }
517    
518            if ( filters.size() == 1 ) {
519                return filters.poll();
520            }
521    
522            StringBuffer sb = new StringBuffer( 512 * filters.size() );
523            sb.append( "<ogc:Or>" );
524            while ( filters.size() > 0 ) {
525                sb.append( filters.poll() );
526            }
527            sb.append( "</ogc:Or>" );
528            return sb;
529        }
530    
531        private static StringBuffer extractSortProperty( SortProperty sp ) {
532            StringBuffer sb = new StringBuffer();
533            sb.append( "<ogc:SortProperty>" );
534            sb.append( "<ogc:PropertyName>" );
535            sb.append( sp.getSortProperty().toString() );
536            sb.append( "</ogc:PropertyName>" );
537            sb.append( "<ogc:SortOrder>" );
538            sb.append( sp.getSortOrder() ? "ASC" : "DESC" );
539            sb.append( "</ogc:SortOrder>" );
540            sb.append( "</ogc:SortProperty>" );
541    
542            return sb;
543        }
544    
545        private static LinkedList<StringBuffer> extractFilters( Filter filter ) {
546            LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>();
547    
548            if ( filter instanceof ComplexFilter ) {
549                filters.add( ( ( (ComplexFilter) filter ).getOperation() ).to110XML() );
550            }
551            if ( filter instanceof FeatureFilter ) {
552                ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
553                for ( int i = 0; i < featureIds.size(); i++ ) {
554                    FeatureId fid = featureIds.get( i );
555                    filters.add( fid.toXML() );
556                }
557            }
558    
559            return filters;
560        }
561    
562        private static void appendCustomFilter( StringBuffer sb, String name, String value ) {
563            String[] vals = value.split( "," );
564            if ( vals.length > 1 ) {
565                sb.append( "<ogc:Or>" );
566            }
567            for ( String val : vals ) {
568                sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" );
569                sb.append( name ).append( "</ogc:PropertyName><ogc:Literal>" );
570                sb.append( val ).append( "</ogc:Literal></ogc:PropertyIsEqualTo>" );
571            }
572            if ( vals.length > 1 ) {
573                sb.append( "</ogc:Or>" );
574            }
575        }
576    
577        /**
578         * transforms the requested BBOX into the DefaultSRS of the assigned feature type
579         * 
580         * @param ds
581         * @return the envelope
582         * @throws OGCWebServiceException
583         * @throws CRSTransformationException
584         * @throws UnknownCRSException
585         */
586        private Envelope transformBBOX( LocalWFSDataSource ds )
587                                throws OGCWebServiceException, CRSTransformationException, UnknownCRSException {
588            Envelope bbox = request.getBoundingBox();
589            // transform request bounding box to the coordinate reference
590            // system the WFS holds the data if requesting CRS and WFS-Data
591            // crs are different
592            OGCWebService service = ds.getOGCWebService();
593            WFSCapabilities capa;
594            if ( service instanceof RemoteWFService ) {
595                RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService();
596                capa = wfs.getWFSCapabilities();
597            } else {
598                WFService wfs = (WFService) service;
599                capa = wfs.getCapabilities();
600            }
601            // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities();
602            QualifiedName gn = ds.getName();
603            WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn );
604    
605            if ( ft == null ) {
606                throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) );
607            }
608    
609            // enable different formatations of the crs encoding for GML geometries
610            String GML_SRS = "http://www.opengis.net/gml/srs/";
611            String old_gml_srs = ft.getDefaultSRS().toASCIIString();
612            String old_srs;
613            if ( old_gml_srs.startsWith( GML_SRS ) ) {
614                old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase();
615            } else {
616                old_srs = old_gml_srs;
617            }
618    
619            String new_srs = request.getSrs();
620            String new_gml_srs;
621            if ( old_gml_srs.startsWith( GML_SRS ) ) {
622                new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase();
623            } else {
624                new_gml_srs = new_srs;
625            }
626    
627            if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) {
628                GeoTransformer transformer = new GeoTransformer( CRSFactory.create( old_srs ) );
629                bbox = transformer.transform( bbox, this.handler.getRequestCRS() );
630            }
631            return bbox;
632        }
633    
634        /**
635         * creates a getCoverage request considering the getMap request and the filterconditions defined in the submitted
636         * <tt>DataSource</tt> object The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>.
637         * 
638         * @param ds
639         *            the datasource
640         * @param request
641         *            the GetMap operation
642         * @return GetCoverage request object
643         * @throws InconsistentRequestException
644         */
645        protected static GetCoverage createGetCoverageRequest( AbstractDataSource ds, GetMap request )
646                                throws InconsistentRequestException {
647    
648            Envelope bbox = request.getBoundingBox();
649    
650            double xres = bbox.getWidth() / request.getWidth();
651            double yres = bbox.getHeight() / request.getHeight();
652    
653            WorldFile tmpWorldFile = new WorldFile( xres, yres, 0.0, 0.0, bbox, WorldFile.TYPE.OUTER );
654            bbox = tmpWorldFile.getEnvelope( WorldFile.TYPE.CENTER );
655    
656            GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest();
657    
658            String crs = request.getSrs();
659            // if (gcr != null && gcr.getDomainSubset().getRequestSRS() != null) {
660            // crs = gcr.getDomainSubset().getRequestSRS().getCode();
661            // }
662            String format = request.getFormat();
663            int pos = format.indexOf( '/' );
664            if ( pos > -1 )
665                format = format.substring( pos + 1, format.length() );
666            if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) {
667                format = gcr.getOutput().getFormat().getCode();
668            }
669            if ( format.indexOf( "svg" ) > -1 ) {
670                format = "tiff";
671            }
672            if ( format.startsWith( "png" ) ) {
673                format = "png";
674            }
675    
676            String version = "1.0.0";
677            if ( gcr != null && gcr.getVersion() != null ) {
678                version = gcr.getVersion();
679            }
680            String lay = ds.getName().getPrefixedName();
681            if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) {
682                lay = gcr.getSourceCoverage();
683            }
684            String ipm = null;
685            if ( gcr != null && gcr.getInterpolationMethod() != null ) {
686                ipm = gcr.getInterpolationMethod().value;
687            }
688    
689            // TODO
690            // handle range sets e.g. time
691            // note that elevation is now handled after the results are in (see the handleGetCoverageResponse method below)
692            StringBuffer sb = new StringBuffer( 1000 );
693            sb.append( "service=WCS&request=GetCoverage" );
694            sb.append( "&version=" ).append( version );
695            sb.append( "&COVERAGE=" ).append( lay );
696            sb.append( "&crs=" ).append( crs );
697            sb.append( "&response_crs=" ).append( crs );
698            sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' );
699            sb.append( bbox.getMin().getY() ).append( ',' ).append( bbox.getMax().getX() );
700            sb.append( ',' ).append( bbox.getMax().getY() );
701            sb.append( "&WIDTH=" ).append( request.getWidth() );
702            sb.append( "&HEIGHT=" ).append( request.getHeight() );
703            sb.append( "&FORMAT=" ).append( format );
704            sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm );
705            try {
706                IDGenerator idg = IDGenerator.getInstance();
707                gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() );
708            } catch ( WCSException e ) {
709                throw new InconsistentRequestException( e.getMessage() );
710            } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) {
711                throw new InconsistentRequestException( e.getMessage() );
712            }
713    
714            LOG.logDebug( "GetCoverage request: " + sb.toString() );
715    
716            return gcr;
717    
718        }
719    
720        /**
721         * 
722         * @param result
723         * @return the response objects
724         * @throws Exception
725         */
726        public Object handleResponse( Object result )
727                                throws Exception {
728    
729            Object theme = null;
730            if ( result instanceof ResultCoverage ) {
731                theme = handleGetCoverageResponse( (ResultCoverage) result );
732            } else if ( result instanceof FeatureResult ) {
733                theme = handleGetFeatureResponse( (FeatureResult) result );
734            } else if ( result instanceof GetMapResult ) {
735                theme = handleGetMapResponse( (GetMapResult) result );
736            } else {
737                String s = Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" );
738                if ( datasource.isFailOnException() ) {
739                    OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s );
740                    theme = exce;
741                }
742            }
743            return theme;
744        }
745    
746        /**
747         * replaces all pixels within the passed image having a color that is defined to be transparent within their
748         * datasource with a transparent color.
749         * 
750         * @param img
751         * @return modified image
752         */
753        private BufferedImage setTransparentColors( BufferedImage img ) {
754    
755            Color[] colors = null;
756            if ( datasource.getType() == AbstractDataSource.LOCALWCS ) {
757                LocalWCSDataSource ds = (LocalWCSDataSource) datasource;
758                colors = ds.getTransparentColors();
759            } else if ( datasource.getType() == AbstractDataSource.REMOTEWCS ) {
760                RemoteWCSDataSource ds = (RemoteWCSDataSource) datasource;
761                colors = ds.getTransparentColors();
762            } else if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) {
763                RemoteWMSDataSource ds = (RemoteWMSDataSource) datasource;
764                colors = ds.getTransparentColors();
765            }
766    
767            if ( colors != null && colors.length > 0 ) {
768    
769                int[] clrs = new int[colors.length];
770                for ( int i = 0; i < clrs.length; i++ ) {
771                    clrs[i] = colors[i].getRGB();
772                }
773    
774                if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) {
775                    // if the incoming image does not allow transparency
776                    // it must be copyed to a image of ARGB type
777                    BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
778                    Graphics g = tmp.getGraphics();
779                    g.drawImage( img, 0, 0, null );
780                    g.dispose();
781                    img = tmp;
782                }
783    
784                // TODO
785                // should be replaced by a JAI operation
786                int w = img.getWidth();
787                int h = img.getHeight();
788                for ( int i = 0; i < w; i++ ) {
789                    for ( int j = 0; j < h; j++ ) {
790                        int col = img.getRGB( i, j );
791                        if ( shouldBeTransparent( colors, col ) ) {
792                            img.setRGB( i, j, 0x00FFFFFF );
793                        }
794                    }
795                }
796    
797            }
798    
799            return img;
800        }
801    
802        /**
803         * @return true if the distance between the image color and at least of the colors to be truned to be transparent is
804         *         less than 3 in an int RGB cube
805         * 
806         * @param colors
807         * @param color
808         */
809        private boolean shouldBeTransparent( Color[] colors, int color ) {
810            Color c2 = new Color( color );
811            int r = c2.getRed();
812            int g = c2.getGreen();
813            int b = c2.getBlue();
814            for ( int i = 0; i < colors.length; i++ ) {
815                int r1 = colors[i].getRed();
816                int g1 = colors[i].getGreen();
817                int b1 = colors[i].getBlue();
818                if ( Math.sqrt( ( r1 - r ) * ( r1 - r ) + ( g1 - g ) * ( g1 - g ) + ( b1 - b ) * ( b1 - b ) ) < 3 ) {
819                    return true;
820                }
821            }
822            return false;
823        }
824    
825        /**
826         * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt>
827         * from it
828         * 
829         * @param response
830         * @return the response objects
831         * @throws Exception
832         */
833        private Object handleGetMapResponse( GetMapResult response )
834                                throws Exception {
835    
836            BufferedImage bi = (BufferedImage) response.getMap();
837    
838            bi = setTransparentColors( bi );
839            GridCoverage gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
840            org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc );
841            Theme theme = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
842            return theme;
843    
844        }
845    
846        /**
847         * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
848         * 
849         * @param response
850         * @return the response objects
851         * @throws Exception
852         */
853        private Object handleGetFeatureResponse( FeatureResult response )
854                                throws Exception {
855            FeatureCollection fc = null;
856    
857            Object o = response.getResponse();
858            if ( o instanceof FeatureCollection ) {
859                fc = (FeatureCollection) o;
860            } else {
861                throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) );
862            }
863            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
864                LOG.logDebug( "result: " + fc );
865                for ( int i = 0; i < fc.size(); ++i ) {
866                    outputGeometries( fc.getFeature( i ) );
867                }
868            }
869    
870            org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( layer.getName(), this.handler.getRequestCRS(),
871                                                                           fc );
872    
873            return MapFactory.createTheme( datasource.getName().getPrefixedName(), fl, new UserStyle[] { style } );
874        }
875    
876        private void outputGeometries( Feature feature ) {
877            if ( feature == null ) {
878                return;
879            }
880            FeatureType ft = feature.getFeatureType();
881            PropertyType[] propertyTypes = ft.getProperties();
882            for ( PropertyType pt : propertyTypes ) {
883                if ( pt.getType() == Types.FEATURE ) {
884                    FeatureProperty[] fp = feature.getProperties( pt.getName() );
885                    if ( fp != null ) {
886                        for ( int i = 0; i < fp.length; i++ ) {
887                            outputGeometries( (Feature) fp[i].getValue() );
888                        }
889                    }
890                } else if ( pt.getType() == Types.GEOMETRY ) {
891                    Geometry g = feature.getDefaultGeometryPropertyValue();
892                    if ( g != null ) {
893                        try {
894                            LOG.logDebug( "geometrie: " + WKTAdapter.export( g ).toString() );
895                        } catch ( GeometryException e ) {
896                            LOG.logDebug( "Geometry couldn't be converted to Well Known Text: " + g );
897                        }
898                    }
899                }
900            }
901        }
902    
903        /**
904         * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it
905         * 
906         * @param response
907         * @return the response objects
908         * @throws Exception
909         */
910        private Object handleGetCoverageResponse( ResultCoverage response )
911                                throws Exception {
912            ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage();
913            Object ro = null;
914            if ( gc != null ) {
915                BufferedImage bi = gc.getAsImage( -1, -1 );
916    
917                bi = setTransparentColors( bi );
918    
919                gc = new ImageGridCoverage( null, request.getBoundingBox(), bi );
920    
921                Dimension[] dims = handler.getConfiguration().getLayer( layer.getName() ).getDimension();
922                DimensionValues elev = request.getDimElev();
923                if ( dims != null ) {
924                    for ( Dimension dim : dims ) {
925                        if ( dim.getName().equalsIgnoreCase( "elevation" ) ) {
926                            if ( elev == null && dim.getDefaultValue() != null ) {
927                                request.setDimElev( new DimensionValues( dim.getDefaultValue() ) );
928                                request.warningHeaders.add( "99 Default value used: " + dim.getName() + "="
929                                                            + dim.getDefaultValue() + " " + dim.getUnits() );
930                                elev = request.getDimElev();
931                            }
932                            if ( elev == null ) {
933                                InvalidParameterValueException towrap;
934                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE",
935                                                                                            dim.getName() ),
936                                                                             MISSINGDIMENSIONVALUE );
937                                throw new WMSExceptionFromWCS( towrap );
938                            }
939                            if ( !dim.isMultipleValues() && elev.hasMultipleValues() ) {
940                                InvalidParameterValueException towrap;
941                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES",
942                                                                                            dim.getName() ),
943                                                                             INVALIDDIMENSIONVALUE );
944                                throw new WMSExceptionFromWCS( towrap );
945                            }
946                            String singleValue = elev.values.peek().value;
947                            if ( elev.values.size() == 1 && singleValue != null ) {
948                                DimensionValues origValues = new DimensionValues( dim.getValues() );
949                                if ( !origValues.includesValue( singleValue ) ) {
950                                    if ( dim.isNearestValue() ) {
951                                        String nearestValue = origValues.getNearestValue( singleValue );
952                                        elev.values.peek().value = nearestValue;
953                                        request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "="
954                                                                    + nearestValue + " " + dim.getUnits() );
955                                    }
956                                }
957                            }
958    
959                            if ( !new DimensionValues( dim.getValues() ).includes( elev ) ) {
960                                InvalidParameterValueException towrap;
961                                towrap = new InvalidParameterValueException( "GetMap", get( "WMS_BAD_DIMENSION_VALUE" ),
962                                                                             INVALIDDIMENSIONVALUE );
963                                throw new WMSExceptionFromWCS( towrap );
964                            }
965                        }
966                    }
967                }
968                org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc, request );
969    
970                ro = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } );
971            } else {
972                throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE",
973                                                                                             datasource.getName() ) );
974            }
975            return ro;
976        }
977    
978        class WMSExceptionFromWCS extends Exception {
979    
980            private static final long serialVersionUID = 8999003296940731523L;
981    
982            OGCWebServiceException wrapped;
983    
984            WMSExceptionFromWCS( OGCWebServiceException towrap ) {
985                wrapped = towrap;
986            }
987    
988        }
989    
990    }