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