001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 }