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