001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/security/owsrequestvalidator/wms/GetMapRequestValidator.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.security.owsrequestvalidator.wms;
037    
038    import static org.deegree.security.drm.model.RightType.GETMAP;
039    
040    import java.net.URL;
041    import java.util.ArrayList;
042    import java.util.HashMap;
043    import java.util.List;
044    import java.util.Map;
045    
046    import org.deegree.datatypes.QualifiedName;
047    import org.deegree.datatypes.Types;
048    import org.deegree.framework.log.ILogger;
049    import org.deegree.framework.log.LoggerFactory;
050    import org.deegree.framework.util.ColorUtils;
051    import org.deegree.framework.util.MapUtils;
052    import org.deegree.framework.util.StringTools;
053    import org.deegree.framework.xml.XMLParsingException;
054    import org.deegree.graphics.sld.AbstractStyle;
055    import org.deegree.graphics.sld.NamedLayer;
056    import org.deegree.graphics.sld.NamedStyle;
057    import org.deegree.graphics.sld.SLDFactory;
058    import org.deegree.graphics.sld.StyledLayerDescriptor;
059    import org.deegree.model.crs.CRSFactory;
060    import org.deegree.model.crs.CoordinateSystem;
061    import org.deegree.model.crs.GeoTransformer;
062    import org.deegree.model.feature.Feature;
063    import org.deegree.model.feature.FeatureFactory;
064    import org.deegree.model.feature.FeatureProperty;
065    import org.deegree.model.feature.schema.FeatureType;
066    import org.deegree.model.feature.schema.PropertyType;
067    import org.deegree.model.spatialschema.Envelope;
068    import org.deegree.model.spatialschema.GeometryFactory;
069    import org.deegree.ogcwebservices.InvalidParameterValueException;
070    import org.deegree.ogcwebservices.OGCWebServiceRequest;
071    import org.deegree.ogcwebservices.wms.operation.GetMap;
072    import org.deegree.security.UnauthorizedException;
073    import org.deegree.security.drm.model.User;
074    import org.deegree.security.owsproxy.Condition;
075    import org.deegree.security.owsproxy.OperationParameter;
076    import org.deegree.security.owsproxy.Request;
077    import org.deegree.security.owsrequestvalidator.Messages;
078    import org.deegree.security.owsrequestvalidator.Policy;
079    
080    /**
081     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
082     * @author last edited by: $Author: mschneider $
083     *
084     * @version 1.1, $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
085     *
086     * @since 1.1
087     */
088    
089    public class GetMapRequestValidator extends AbstractWMSRequestValidator {
090    
091        private static final ILogger LOG = LoggerFactory.getLogger( GetMapRequestValidator.class );
092    
093        // known condition parameter
094        private static final String BBOX = "bbox";
095    
096        private static final String LAYERS = "layers";
097    
098        private static final String BGCOLOR = "bgcolor";
099    
100        private static final String TRANSPARENCY = "transparency";
101    
102        private static final String RESOLUTION = "resolution";
103    
104        private static final String SLD = "sld";
105    
106        private static final String INVALIDBBOX = Messages.getString( "GetMapRequestValidator.INVALIDBBOX" );
107    
108        private static final String INVALIDLAYER = Messages.getString( "GetMapRequestValidator.INVALIDLAYER" );
109    
110        private static final String INVALIDSTYLE = Messages.getString( "GetMapRequestValidator.INVALIDSTYLE" );
111    
112        private static final String INVALIDBGCOLOR = Messages.getString( "GetMapRequestValidator.INVALIDBGCOLOR" );
113    
114        private static final String INVALIDTRANSPARENCY = Messages.getString( "GetMapRequestValidator.INVALIDTRANSPARENCY" );
115    
116        private static final String INVALIDRESOLUTION = Messages.getString( "GetMapRequestValidator.INVALIDRESOLUTION" );
117    
118        private static final String INVALIDSLD = Messages.getString( "GetMapRequestValidator.INVALIDSLD" );
119    
120        private static final String MISSINGCRS = Messages.getString( "GetMapRequestValidator.MISSINGCRS" );
121    
122        private List<String> accessdRes = new ArrayList<String>();
123    
124        private static FeatureType mapFT = null;
125    
126        private GeoTransformer gt = null;
127    
128        static {
129            if ( mapFT == null ) {
130                mapFT = GetMapRequestValidator.createFeatureType();
131            }
132        }
133    
134        /**
135         * @param policy
136         */
137        public GetMapRequestValidator( Policy policy ) {
138            super( policy );
139            try {
140                gt = new GeoTransformer( "EPSG:4326" );
141            } catch ( Exception e ) {
142                e.printStackTrace();
143            }
144        }
145    
146        /**
147         * validates the incoming GetMap request against the policy assigned to a validator
148         *
149         * @param request
150         *            request to validate
151         * @param user
152         *            name of the user who likes to perform the request (can be null)
153         */
154        @Override
155        public void validateRequest( OGCWebServiceRequest request, User user )
156                                throws InvalidParameterValueException, UnauthorizedException {
157    
158            accessdRes.clear();
159            userCoupled = false;
160            Request req = policy.getRequest( "WMS", "GetMap" );
161            // request is valid because no restrictions are made
162            if ( req.isAny() || req.getPreConditions().isAny() ) {
163                return;
164            }
165            Condition condition = req.getPreConditions();
166    
167            GetMap wmsreq = (GetMap) request;
168    
169            validateVersion( condition, wmsreq.getVersion() );
170            Envelope env = wmsreq.getBoundingBox();
171            try {
172                env = gt.transform( env, wmsreq.getSrs() );
173            } catch ( Exception e ) {
174                throw new InvalidParameterValueException( "condition envelope isn't in the right CRS ", e );
175            }
176            validateBBOX( condition, env );
177            validateLayers( condition, wmsreq.getLayers() );
178            validateBGColor( condition, ColorUtils.toHexCode( "0x", wmsreq.getBGColor() ) );
179            validateTransparency( condition, wmsreq.getTransparency() );
180            validateExceptions( condition, wmsreq.getExceptions() );
181            validateFormat( condition, wmsreq.getFormat() );
182            validateMaxWidth( condition, wmsreq.getWidth() );
183            validateMaxHeight( condition, wmsreq.getHeight() );
184            validateResolution( condition, wmsreq );
185            validateSLD( condition, wmsreq.getSLD_URL() );
186            validateSLD_Body( condition, wmsreq.getStyledLayerDescriptor() );
187    
188            if ( userCoupled ) {
189                validateAgainstRightsDB( wmsreq, user );
190            }
191    
192        }
193    
194        /**
195         * checks if the passed envelope is valid against the maximum bounding box defined in the policy. If
196         * <tt>user</ff> != <tt>null</tt> the maximu valid BBOX will be read from the user/rights repository
197         *
198         * @param condition
199         *            condition containing the definition of the valid BBOX
200         * @param envelope
201         * @throws InvalidParameterValueException
202         */
203        private void validateBBOX( Condition condition, Envelope envelope )
204                                throws InvalidParameterValueException {
205    
206            OperationParameter op = condition.getOperationParameter( BBOX );
207    
208            // version is valid because no restrictions are made
209            if ( op.isAny() )
210                return;
211    
212            String v = op.getFirstAsString();
213            String[] d = StringTools.toArray( v, ",", false );
214            Envelope env = GeometryFactory.createEnvelope( Double.parseDouble( d[0] ), Double.parseDouble( d[1] ),
215                                                           Double.parseDouble( d[2] ), Double.parseDouble( d[3] ), null );
216    
217            try {
218                env = gt.transform( env, d[4] );
219            } catch ( Exception e ) {
220                throw new InvalidParameterValueException( MISSINGCRS, e );
221            }
222    
223            if ( !env.contains( envelope ) ) {
224                if ( !op.isUserCoupled() ) {
225                    // if not user coupled the validation has failed
226                    throw new InvalidParameterValueException( INVALIDBBOX + op.getFirstAsString() );
227                }
228                userCoupled = true;
229                accessdRes.add( "BBOX: " + v );
230            }
231        }
232    
233        /**
234         * checks if the passed layres/styles are valid against the layers/styles list defined in the policy. If
235         * <tt>user</ff> != <tt>null</tt> the valid layers/styles will be read from the user/rights repository
236         *
237         * @param condition
238         *            condition containing the definition of the valid layers/styles
239         * @param layers
240         * @throws InvalidParameterValueException
241         */
242        private void validateLayers( Condition condition, GetMap.Layer[] layers )
243                                throws InvalidParameterValueException {
244    
245            OperationParameter op = condition.getOperationParameter( LAYERS );
246    
247            // version is valid because no restrictions are made
248            if ( op.isAny() ) {
249                return;
250            }
251    
252            List<String> v = op.getValues();
253            // seperate layers from assigned styles
254            Map<String, String> map = new HashMap<String, String>();
255            for ( int i = 0; i < v.size(); i++ ) {
256                String[] tmp = StringTools.toArray( v.get( i ), "|", false );
257                map.put( tmp[0], tmp[1] );
258            }
259    
260            for ( int i = 0; i < layers.length; i++ ) {
261                String style = layers[i].getStyleName();
262                String vs = map.get( layers[i].getName() );
263                if ( vs == null ) {
264                    if ( !op.isUserCoupled() ) {
265                        throw new InvalidParameterValueException( INVALIDLAYER + layers[i].getName() );
266                    }
267                    accessdRes.add( "Layers: " + layers[i].getName() );
268                    userCoupled = true;
269                } else if ( !style.equalsIgnoreCase( "default" ) && vs.indexOf( "$any$" ) < 0 && vs.indexOf( style ) < 0 ) {
270                    // a style is valid for a layer if it's the default style
271                    // or the layer accepts any style or a style is explicit defined
272                    // to be valid
273                    if ( !op.isUserCoupled() ) {
274                        throw new InvalidParameterValueException( INVALIDSTYLE + layers[i].getName() + ':' + style );
275                    }
276                    userCoupled = true;
277                    accessdRes.add( "Styles: " + style );
278                }
279            }
280    
281        }
282    
283        /**
284         * checks if the passed bgcolor is valid against the bgcolor(s) defined in the policy. If
285         * <tt>user</ff> != <tt>null</tt> the valid bgcolors will be read from the user/rights repository
286         *
287         * @param condition
288         *            condition containing the definition of the valid bgcolors
289         * @param bgcolor
290         * @throws InvalidParameterValueException
291         */
292        private void validateBGColor( Condition condition, String bgcolor )
293                                throws InvalidParameterValueException {
294    
295            OperationParameter op = condition.getOperationParameter( BGCOLOR );
296    
297            // version is valid because no restrictions are made
298            if ( op.isAny() )
299                return;
300    
301            List<String> list = op.getValues();
302    
303            if ( !list.contains( bgcolor ) ) {
304                if ( !op.isUserCoupled() ) {
305                    throw new InvalidParameterValueException( INVALIDBGCOLOR + bgcolor );
306                }
307                accessdRes.add( "BGCOLOR" + bgcolor );
308                userCoupled = true;
309            }
310    
311        }
312    
313        /**
314         * checks if the passed transparency is valid against the transparency defined in the policy. If
315         * <tt>user</ff> != <tt>null</tt> the valid transparency will be read from the user/rights repository
316         *
317         * @param condition
318         *            condition containing the definition of the valid transparency
319         * @param transparency
320         * @throws InvalidParameterValueException
321         */
322        private void validateTransparency( Condition condition, boolean transparency )
323                                throws InvalidParameterValueException {
324    
325            OperationParameter op = condition.getOperationParameter( TRANSPARENCY );
326    
327            // version is valid because no restrictions are made
328            if ( op.isAny() )
329                return;
330    
331            List<String> v = op.getValues();
332            String s = "" + transparency;
333            if ( !v.get( 0 ).equals( s ) && !v.get( v.size() - 1 ).equals( s ) ) {
334                if ( !op.isUserCoupled() ) {
335                    throw new InvalidParameterValueException( INVALIDTRANSPARENCY + transparency );
336                }
337                userCoupled = true;
338                accessdRes.add( "Transparency: " + transparency );
339            }
340    
341        }
342    
343        /**
344         * checks if the requested map area/size is valid against the minimum resolution defined in the policy. If
345         * <tt>user</ff> != <tt>null</tt> the valid resolution will be read from the user/rights repository
346         *
347         * @param condition
348         *            condition containing the definition of the valid resolution
349         * @throws InvalidParameterValueException
350         */
351        private void validateResolution( Condition condition, GetMap gmr )
352                                throws InvalidParameterValueException {
353    
354            OperationParameter op = condition.getOperationParameter( RESOLUTION );
355    
356            // version is valid because no restrictions are made
357            if ( op.isAny() )
358                return;
359    
360            double scale = 0;
361            try {
362                scale = calcScale( gmr );
363            } catch ( Exception e ) {
364                throw new InvalidParameterValueException( StringTools.stackTraceToString( e ) );
365            }
366            double compareRes = 0;
367            compareRes = op.getFirstAsDouble();
368            if ( scale < compareRes ) {
369                if ( !op.isUserCoupled() ) {
370                    throw new InvalidParameterValueException( INVALIDRESOLUTION + scale );
371                }
372                userCoupled = true;
373                accessdRes.add( "resolution: " + scale );
374            }
375        }
376    
377        /**
378         * checks if the passed reference to a SLD document is valid against the defined in the policy. If
379         * <tt>user</ff> != <tt>null</tt> the valid sld reference addresses will be read from the user/rights repository
380         *
381         * @param condition
382         *            condition containing the definition of the valid sldRef
383         * @param sldRef
384         * @throws InvalidParameterValueException
385         */
386        private void validateSLD( Condition condition, URL sldRef )
387                                throws InvalidParameterValueException {
388    
389            OperationParameter op = condition.getOperationParameter( SLD );
390            OperationParameter gmop = condition.getOperationParameter( LAYERS );
391    
392            if ( op == null && sldRef != null ) {
393                throw new InvalidParameterValueException( INVALIDSLD + sldRef );
394            }
395            // sldRef is valid because no restrictions are made
396            if ( sldRef == null || op.isAny() ) {
397                return;
398            }
399    
400            // validate reference base of the SLD
401            List<String> list = op.getValues();
402            String port = null;
403            if ( sldRef.getPort() != -1 ) {
404                port = ":" + sldRef.getPort();
405            } else {
406                port = ":80";
407            }
408            String addr = sldRef.getProtocol() + "://" + sldRef.getHost() + port;
409            if ( !list.contains( addr ) ) {
410                if ( !op.isUserCoupled() ) {
411                    throw new InvalidParameterValueException( INVALIDSLD + sldRef );
412                }
413                userCoupled = true;
414            }
415    
416            // validate referenced dacument to be a valid SLD
417            StyledLayerDescriptor sld = null;
418            try {
419                sld = SLDFactory.createSLD( sldRef );
420            } catch ( XMLParsingException e ) {
421                String s = org.deegree.i18n.Messages.getMessage( "WMS_SLD_IS_NOT_VALID", sldRef );
422                throw new InvalidParameterValueException( s );
423            }
424    
425            // validate NamedLayers referenced by the SLD
426            NamedLayer[] nl = sld.getNamedLayers();
427            List<String> v = gmop.getValues();
428            // seperate layers from assigned styles
429            Map<String, String> map = new HashMap<String, String>();
430            for ( int i = 0; i < v.size(); i++ ) {
431                String[] tmp = StringTools.toArray( v.get( i ), "|", false );
432                map.put( tmp[0], tmp[1] );
433            }
434            if ( !userCoupled ) {
435                for ( int i = 0; i < nl.length; i++ ) {
436                    AbstractStyle st = nl[i].getStyles()[0];
437                    String style = null;
438                    if ( st instanceof NamedStyle ) {
439                        style = ( (NamedStyle) st ).getName();
440                    } else {
441                        // use default as name if a UserStyle is defined
442                        // to ensure that the style will be accepted by
443                        // the validator
444                        style = "default";
445                    }
446                    String vs = map.get( nl[i].getName() );
447                    if ( vs == null ) {
448                        if ( !op.isUserCoupled() ) {
449                            throw new InvalidParameterValueException( INVALIDLAYER + nl[i].getName() );
450                        }
451                        accessdRes.add( "Layers: " + nl[i].getName() );
452                        userCoupled = true;
453                    } else if ( !style.equalsIgnoreCase( "default" ) && vs.indexOf( "$any$" ) < 0
454                                && vs.indexOf( style ) < 0 ) {
455                        // a style is valid for a layer if it's the default style
456                        // or the layer accepts any style or a style is explicit defined
457                        // to be valid
458                        if ( !op.isUserCoupled() ) {
459                            throw new InvalidParameterValueException( INVALIDSTYLE + nl[i].getName() + ':' + style );
460                        }
461                        userCoupled = true;
462                        accessdRes.add( "Styles: " + style );
463                    }
464                }
465            }
466    
467        }
468    
469        /**
470         * checks if the passed user is allowed to perform a GetMap request containing a SLD_BODY parameter.
471         *
472         * @param condition
473         *            condition containing when SLD_BODY is valid or nots
474         * @param sld_body
475         */
476        private void validateSLD_Body( Condition condition, StyledLayerDescriptor sld_body ) {
477    
478            /*
479             *
480             * OperationParameter op = condition.getOperationParameter( SLD_BODY ); // version is valid because no
481             * restrictions are made if ( sld_body == null ||op.isAny() ) return; // at the moment it is just evaluated if
482             * the user is allowed // to perform a SLD request or not. no content validation will // be made boolean
483             * isAllowed = false; if ( op.isUserCoupled() ) { //TODO // get comparator list from security registry } if
484             * (!isAllowed ) { throw new InvalidParameterValueException( INVALIDSLD_BODY ); }
485             */
486        }
487    
488        /**
489         * validates the passed WMS GetMap request against a User- and Rights-Management DB.
490         *
491         * @param wmsreq
492         * @param user
493         * @throws InvalidParameterValueException
494         */
495        private void validateAgainstRightsDB( GetMap wmsreq, User user )
496                                throws InvalidParameterValueException, UnauthorizedException {
497    
498            if ( user == null ) {
499                StringBuffer sb = new StringBuffer( 1000 );
500                sb.append( ' ' );
501                for ( int i = 0; i < accessdRes.size(); i++ ) {
502                    sb.append( accessdRes.get( i ) ).append( "; " );
503                }
504                throw new UnauthorizedException( Messages.format( "RequestValidator.NOACCESS", sb ) );
505            }
506    
507            Double scale = null;
508            try {
509                scale = new Double( calcScale( wmsreq ) );
510            } catch ( Exception e ) {
511                throw new InvalidParameterValueException( e );
512            }
513    
514            // create feature that describes the map request
515            FeatureProperty[] fps = new FeatureProperty[11];
516            fps[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "version" ), wmsreq.getVersion() );
517            fps[1] = FeatureFactory.createFeatureProperty( new QualifiedName( "width" ), new Integer( wmsreq.getWidth() ) );
518            fps[2] = FeatureFactory.createFeatureProperty( new QualifiedName( "height" ), new Integer( wmsreq.getHeight() ) );
519            Envelope env = wmsreq.getBoundingBox();
520            try {
521                env = gt.transform( env, wmsreq.getSrs() );
522            } catch ( Exception e ) {
523                throw new InvalidParameterValueException( "A:condition envelope isn't in the right CRS ", e );
524            }
525            Object geom = null;
526            try {
527                geom = GeometryFactory.createSurface( env, null );
528            } catch ( Exception e1 ) {
529                e1.printStackTrace();
530            }
531            fps[3] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), geom );
532            fps[4] = FeatureFactory.createFeatureProperty( new QualifiedName( "format" ), wmsreq.getFormat() );
533            fps[5] = FeatureFactory.createFeatureProperty( new QualifiedName( "bgcolor" ),
534                                                           ColorUtils.toHexCode( "0x", wmsreq.getBGColor() ) );
535            fps[6] = FeatureFactory.createFeatureProperty( new QualifiedName( "transparent" ), ""
536                                                                                               + wmsreq.getTransparency() );
537            fps[7] = FeatureFactory.createFeatureProperty( new QualifiedName( "exceptions" ), wmsreq.getExceptions() );
538            fps[8] = FeatureFactory.createFeatureProperty( new QualifiedName( "resolution" ), scale );
539            fps[9] = FeatureFactory.createFeatureProperty( new QualifiedName( "sld" ), wmsreq.getSLD_URL() );
540    
541            GetMap.Layer[] layers = wmsreq.getLayers();
542            for ( int i = 0; i < layers.length; i++ ) {
543                fps[10] = FeatureFactory.createFeatureProperty( new QualifiedName( "style" ), layers[i].getStyleName() );
544                Feature feature = FeatureFactory.createFeature( "id", mapFT, fps );
545    
546                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
547                    for ( int ii = 0; ii < fps.length; ii++ ) {
548                        LOG.logDebug( "compared property: ", fps[ii] );
549                    }
550                }
551    
552                if ( securityConfig.getProxiedUrl() == null ) {
553                    handleUserCoupledRules( user, feature, layers[i].getName(), "Layer", GETMAP );
554                } else {
555                    handleUserCoupledRules( user, feature, "[" + securityConfig.getProxiedUrl() + "]:"
556                                                           + layers[i].getName(), "Layer", GETMAP );
557                }
558            }
559    
560        }
561    
562        /**
563         * calculates the map scale as defined in the OGC WMS 1.1.1 specifications
564         *
565         * @return scale of the map
566         */
567        private double calcScale( GetMap request )
568                                throws Exception {
569    
570            Envelope bbox = request.getBoundingBox();
571    
572            CoordinateSystem crs = CRSFactory.create( request.getSrs() );
573            return MapUtils.calcScale( request.getWidth(), request.getHeight(), bbox, crs, 1 );
574            // would return scale denominator
575            // return MapUtils.calcScale( request.getWidth(), request.getHeight(), bbox, crs, DEFAULT_PIXEL_SIZE );
576        }
577    
578        private static FeatureType createFeatureType() {
579            PropertyType[] ftps = new PropertyType[11];
580            ftps[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "version" ), Types.VARCHAR, false );
581            ftps[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( "width" ), Types.INTEGER, false );
582            ftps[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( "height" ), Types.INTEGER, false );
583            ftps[3] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false );
584            ftps[4] = FeatureFactory.createSimplePropertyType( new QualifiedName( "format" ), Types.VARCHAR, false );
585            ftps[5] = FeatureFactory.createSimplePropertyType( new QualifiedName( "bgcolor" ), Types.VARCHAR, false );
586            ftps[6] = FeatureFactory.createSimplePropertyType( new QualifiedName( "transparent" ), Types.VARCHAR, false );
587            ftps[7] = FeatureFactory.createSimplePropertyType( new QualifiedName( "exceptions" ), Types.VARCHAR, false );
588            ftps[8] = FeatureFactory.createSimplePropertyType( new QualifiedName( "resolution" ), Types.DOUBLE, false );
589            ftps[9] = FeatureFactory.createSimplePropertyType( new QualifiedName( "sld" ), Types.VARCHAR, false );
590            ftps[10] = FeatureFactory.createSimplePropertyType( new QualifiedName( "style" ), Types.VARCHAR, false );
591    
592            return FeatureFactory.createFeatureType( "GetMap", false, ftps );
593        }
594    
595    }