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