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 }