001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/portal/portlet/modules/wfs/actions/portlets/WFSClientPortletPerform.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.portal.portlet.modules.wfs.actions.portlets; 037 038 import java.io.BufferedReader; 039 import java.io.ByteArrayInputStream; 040 import java.io.ByteArrayOutputStream; 041 import java.io.File; 042 import java.io.FileReader; 043 import java.io.IOException; 044 import java.io.InputStream; 045 import java.io.StringReader; 046 import java.io.UnsupportedEncodingException; 047 import java.net.URL; 048 import java.nio.charset.Charset; 049 import java.util.HashMap; 050 import java.util.Iterator; 051 import java.util.Map; 052 053 import javax.servlet.ServletContext; 054 import javax.servlet.http.HttpServletRequest; 055 056 import org.apache.commons.httpclient.HttpClient; 057 import org.apache.commons.httpclient.methods.PostMethod; 058 import org.apache.commons.httpclient.methods.StringRequestEntity; 059 import org.apache.jetspeed.portal.Portlet; 060 import org.deegree.datatypes.Types; 061 import org.deegree.enterprise.WebUtils; 062 import org.deegree.enterprise.control.RPCException; 063 import org.deegree.enterprise.control.RPCFactory; 064 import org.deegree.enterprise.control.RPCMethodCall; 065 import org.deegree.enterprise.control.RPCParameter; 066 import org.deegree.enterprise.control.RPCStruct; 067 import org.deegree.enterprise.control.RPCUtils; 068 import org.deegree.framework.log.ILogger; 069 import org.deegree.framework.log.LoggerFactory; 070 import org.deegree.framework.util.StringTools; 071 import org.deegree.framework.xml.XMLFragment; 072 import org.deegree.framework.xml.XSLTDocument; 073 import org.deegree.model.crs.CRSFactory; 074 import org.deegree.model.crs.CoordinateSystem; 075 import org.deegree.model.crs.GeoTransformer; 076 import org.deegree.model.crs.UnknownCRSException; 077 import org.deegree.model.feature.Feature; 078 import org.deegree.model.feature.FeatureCollection; 079 import org.deegree.model.feature.FeatureFactory; 080 import org.deegree.model.feature.FeatureProperty; 081 import org.deegree.model.feature.GMLFeatureAdapter; 082 import org.deegree.model.feature.GMLFeatureCollectionDocument; 083 import org.deegree.model.feature.schema.FeatureType; 084 import org.deegree.model.spatialschema.Geometry; 085 import org.deegree.ogcwebservices.OGCWebServiceException; 086 import org.deegree.ogcwebservices.OWSUtils; 087 import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException; 088 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 089 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilitiesDocument; 090 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 091 import org.deegree.portal.PortalException; 092 import org.deegree.portal.portlet.modules.actions.IGeoPortalPortletPerform; 093 094 /** 095 * 096 * 097 * @version $Revision: 18195 $ 098 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 099 * @author last edited by: $Author: mschneider $ 100 * 101 * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $ 102 * 103 * @since 2.0 104 */ 105 public class WFSClientPortletPerform extends IGeoPortalPortletPerform { 106 107 private static final ILogger LOG = LoggerFactory.getLogger( WFSClientPortletPerform.class ); 108 109 protected static final String INIT_TARGETSRS = "TARGETSRS"; 110 111 protected static final String INIT_XSLT = "XSLT"; 112 113 private static Map<String, WFSCapabilities> capaMap = new HashMap<String, WFSCapabilities>(); 114 115 /** 116 * @param request 117 * @param portlet 118 * @param servletContext 119 */ 120 public WFSClientPortletPerform( HttpServletRequest request, Portlet portlet, ServletContext servletContext ) { 121 super( request, portlet, servletContext ); 122 123 } 124 125 protected void doGetfeature() 126 throws PortalException, OGCWebServiceException { 127 128 RPCParameter[] rpcParams = extractRPCParameters(); 129 Map<String, FeatureCollection> allFCs = new HashMap<String, FeatureCollection>(); 130 for ( int i = 1; i < rpcParams.length; i++ ) { 131 // first field will be skipped because it contains informations 132 // about the desired result format 133 RPCStruct struct = (RPCStruct) rpcParams[i].getValue(); 134 135 String tmp = RPCUtils.getRpcPropertyAsString( struct, "featureTypes" ); 136 137 String[] arr = StringTools.toArray( tmp, ",", true ); 138 String[] xmlns = new String[arr.length]; 139 String[] featureTypes = new String[arr.length]; 140 for ( int j = 0; j < arr.length; j++ ) { 141 int p = arr[j].lastIndexOf( ':' ); 142 xmlns[j] = arr[j].substring( 0, p ); 143 featureTypes[j] = arr[j].substring( p + 1, arr[j].length() ); 144 } 145 146 for ( int j = 0; j < featureTypes.length; j++ ) { 147 148 String query = createQuery( struct, xmlns, featureTypes ); 149 150 LOG.logDebug( "queried feature type: " + xmlns[j] + featureTypes[j] ); 151 LOG.logDebug( "Query: \n" + query ); 152 153 Map<String, FeatureCollection> fcs = null; 154 155 try { 156 fcs = performQuery( featureTypes[j], xmlns[j], query ); 157 } catch ( UnsupportedEncodingException e ) { 158 LOG.logError( e.getMessage(), e ); 159 throw new PortalException( e.getMessage(), e ); 160 } 161 162 if ( getInitParam( INIT_TARGETSRS ) != null ) { 163 Iterator<String> iter = fcs.keySet().iterator(); 164 while ( iter.hasNext() ) { 165 String key = iter.next(); 166 FeatureCollection tmpFc = fcs.get( key ); 167 fcs.put( key, transformGeometries( tmpFc ) ); 168 } 169 } 170 171 allFCs.putAll( fcs ); 172 173 } 174 175 } 176 writeGetFeatureResult( allFCs, (String) rpcParams[0].getValue() ); 177 } 178 179 /** 180 * creates a WFS query depending on requested construction type 181 * 182 * @param struct 183 * @param xmlns 184 * @param featureTypes 185 * @return the query 186 * @throws PortalException 187 */ 188 private String createQuery( RPCStruct struct, String[] xmlns, String[] featureTypes ) 189 throws PortalException { 190 String query = null; 191 String template = RPCUtils.getRpcPropertyAsString( struct, "queryTemplate" ); 192 if ( template != null ) { 193 RPCParameter[] filterProps = null; 194 if ( struct.getMember( "filterProperties" ) != null ) { 195 filterProps = (RPCParameter[]) struct.getMember( "filterProperties" ).getValue(); 196 } 197 query = createQueryFromTemplate( template, filterProps ); 198 } else if ( parameter.get( "FILTER" ) != null ) { 199 String filter = parameter.get( "FILTER" ); 200 query = createQueryFromFilter( featureTypes, xmlns, filter ); 201 } else { 202 String filter = createFilterFromProperties(); 203 query = createQueryFromFilter( featureTypes, xmlns, filter ); 204 } 205 return query; 206 } 207 208 /** 209 * extracts the 210 * 211 * @see RPCParameter array from the RPC method call 212 * @return the array 213 * @throws PortalException 214 */ 215 protected RPCParameter[] extractRPCParameters() 216 throws PortalException { 217 String tmp = parameter.get( "RPC" ); 218 219 StringReader sr = new StringReader( tmp ); 220 RPCMethodCall rpcMethod = null; 221 try { 222 rpcMethod = RPCFactory.createRPCMethodCall( sr ); 223 } catch ( RPCException e ) { 224 LOG.logError( e.getMessage(), e ); 225 throw new PortalException( e.getMessage() ); 226 } 227 228 RPCParameter[] rpcParams = rpcMethod.getParameters(); 229 return rpcParams; 230 } 231 232 /** 233 * performs a transaction against a WFS-T or a database. The backend type to be used by a 234 * transaction depends on a portlets initParameters. 235 * 236 */ 237 public void doTransaction() { 238 System.out.println( parameter ); 239 // throw new UnsupportedOperationException(); 240 } 241 242 /** 243 * writes the result into the forwarded request object 244 * 245 * @param xml 246 * @param fc 247 * @throws PortalException 248 */ 249 private void writeGetFeatureResult( Map<String, FeatureCollection> fcs, String format ) 250 throws PortalException { 251 if ( "XML".equals( format ) ) { 252 XMLFragment xml = new XMLFragment(); 253 254 if ( fcs != null ) { 255 FeatureCollection fc = FeatureFactory.createFeatureCollection( "ID", 1000 ); 256 Iterator<String> iter = fcs.keySet().iterator(); 257 while ( iter.hasNext() ) { 258 fc.addAll( fcs.get( iter.next() ) ); 259 } 260 ByteArrayOutputStream bos = new ByteArrayOutputStream( 100000 ); 261 try { 262 new GMLFeatureAdapter().export( fc, bos ); 263 xml.load( new ByteArrayInputStream( bos.toByteArray() ), XMLFragment.DEFAULT_URL ); 264 } catch ( Exception e ) { 265 LOG.logError( e.getMessage(), e ); 266 throw new PortalException( "could not export feature collection as GML", e ); 267 } 268 if ( getInitParam( INIT_XSLT ) != null ) { 269 xml = transform( xml ); 270 } 271 } 272 273 request.setAttribute( "RESULT", xml ); 274 } else { 275 request.setAttribute( "RESULT", fcs ); 276 } 277 } 278 279 /** 280 * transforms the result of a WFS request using the XSLT script defined by an init parameter 281 * 282 * @param xml 283 * @return the transformed XML 284 * @throws PortalException 285 */ 286 private XMLFragment transform( XMLFragment xml ) 287 throws PortalException { 288 String xslF = getInitParam( INIT_XSLT ); 289 File file = new File( xslF ); 290 if ( !file.isAbsolute() ) { 291 file = new File( sc.getRealPath( xslF ) ); 292 } 293 XSLTDocument xslt = new XSLTDocument(); 294 try { 295 xslt.load( file.toURI().toURL() ); 296 xml = xslt.transform( xml ); 297 } catch ( Exception e ) { 298 LOG.logError( e.getMessage(), e ); 299 throw new PortalException( "could not transform result of WFS request", e ); 300 } 301 return xml; 302 } 303 304 /** 305 * transforms the geometry properties of the features contained in the passed feature collection 306 * into the target CRS given by an init parameter 307 * 308 * @param fc 309 * @return the transformed feature collection 310 * @throws PortalException 311 */ 312 private FeatureCollection transformGeometries( FeatureCollection fc ) 313 throws PortalException { 314 String cs = getInitParam( INIT_TARGETSRS ); 315 CoordinateSystem crs; 316 try { 317 crs = CRSFactory.create( cs ); 318 } catch ( UnknownCRSException e1 ) { 319 throw new PortalException( e1.getMessage(), e1 ); 320 } 321 if ( crs == null ) { 322 throw new PortalException( "CRS: " + cs + " is not known by deegree" ); 323 } 324 try { 325 GeoTransformer gt = new GeoTransformer( crs ); 326 for ( int i = 0; i < fc.size(); i++ ) { 327 Feature feature = fc.getFeature( i ); 328 FeatureType ft = feature.getFeatureType(); 329 FeatureProperty[] fp = feature.getProperties(); 330 for ( int j = 0; j < fp.length; j++ ) { 331 if ( ft.getProperty( fp[j].getName() ).getType() == Types.GEOMETRY ) { 332 Geometry geom = (Geometry) fp[j].getValue(); 333 if ( !crs.equals( geom.getCoordinateSystem() ) ) { 334 geom = gt.transform( geom ); 335 fp[j].setValue( geom ); 336 } 337 } 338 } 339 } 340 } catch ( Exception e ) { 341 LOG.logError( e.getMessage(), e ); 342 throw new PortalException( "could not transform geometries to target CRS: " + cs, e ); 343 } 344 return fc; 345 } 346 347 /** 348 * performs a GetFeature query against one or more WFS's 349 * 350 * @param featureType 351 * @param namespace 352 * @param query 353 * @return the map 354 * @throws OGCWebServiceException 355 * @throws UnsupportedEncodingException 356 */ 357 private Map<String, FeatureCollection> performQuery( String featureType, String namespace, String query ) 358 throws OGCWebServiceException, UnsupportedEncodingException { 359 // WFS to contact 360 String addr = getInitParam( namespace + ':' + featureType ); 361 if ( addr == null ) { 362 // if a client does not send the name of the target WFS 363 // 'WFS' will be used to get the target WFS address from 364 // the portlets init-parameter 365 addr = getInitParam( "WFS" ); 366 } 367 if ( addr == null ) { 368 throw new OGCWebServiceException( "WFS: " + namespace + ':' + featureType + " is not known by the portal" ); 369 } 370 371 // a featuretype may be assigned to more than one WFS 372 String[] addresses = StringTools.toArray( addr, ",", false ); 373 Map<String, FeatureCollection> docs = new HashMap<String, FeatureCollection>(); 374 for ( int i = 0; i < addresses.length; i++ ) { 375 if ( capaMap.get( addresses[i] ) == null ) { 376 // if the WFS Capabilities has not already been read from this 377 // address it will be done now. The result will be stored in the 378 // static Map 'capaMap' to be available at the next call 379 loadWFSCapabilities( addresses[i] ); 380 } 381 382 URL url = OWSUtils.getHTTPPostOperationURL( capaMap.get( addresses[i] ), GetFeature.class ); 383 384 LOG.logDebug( "performing query: ", query ); 385 StringRequestEntity re = new StringRequestEntity( query, "text/xml", Charset.defaultCharset().toString() ); 386 PostMethod post = new PostMethod( url.toExternalForm() ); 387 post.setRequestEntity( re ); 388 InputStream is = null; 389 try { 390 HttpClient client = new HttpClient(); 391 client = WebUtils.enableProxyUsage( client, url ); 392 client.executeMethod( post ); 393 is = post.getResponseBodyAsStream(); 394 } catch ( Exception e ) { 395 LOG.logInfo( url.toExternalForm() ); 396 LOG.logError( e.getMessage(), e ); 397 throw new OGCWebServiceException( "could not perform query against the WFS: " + namespace + ':' 398 + featureType ); 399 } 400 try { 401 GMLFeatureCollectionDocument xml = new GMLFeatureCollectionDocument(); 402 xml.load( is, addresses[i] ); 403 // put the result on a Map that will be forced to the client 404 // which is responsible for what to do with it. Because the keys 405 // of the Map are the WFS addresses the client is able to reconstruct 406 // the source of the result parts 407 docs.put( addresses[i], xml.parse() ); 408 } catch ( Exception e ) { 409 LOG.logError( e.getMessage(), e ); 410 throw new OGCWebServiceException( "could not parse response from WFS: " + namespace + ':' + featureType 411 + " as XML" ); 412 } 413 } 414 return docs; 415 } 416 417 /** 418 * performs a GetCapabilities request against the passed address and stores the result (if it is 419 * a valid WFS capabilities document) in a static Map. 420 * 421 * @param addr 422 * @throws OGCWebServiceException 423 * @throws InvalidCapabilitiesException 424 */ 425 private void loadWFSCapabilities( String addr ) 426 throws OGCWebServiceException, InvalidCapabilitiesException { 427 428 LOG.logDebug( "reading capabilities from: ", addr ); 429 WFSCapabilitiesDocument doc = new WFSCapabilitiesDocument(); 430 try { 431 doc.load( new URL( OWSUtils.validateHTTPGetBaseURL( addr ) 432 + "version=1.1.0&service=WFS&request=GetCapabilities" ) ); 433 } catch ( Exception e ) { 434 LOG.logInfo( OWSUtils.validateHTTPGetBaseURL( addr ) + "version=1.1.0&service=WFS&request=GetCapabilities" ); 435 LOG.logError( e.getMessage(), e ); 436 throw new OGCWebServiceException( "could not read capabilities from WFS: " + addr ); 437 } 438 WFSCapabilities capa = (WFSCapabilities) doc.parseCapabilities(); 439 capaMap.put( addr, capa ); 440 } 441 442 /** 443 * creates a WFS GetFeature query from a named template and a set of KVP-encoded properties 444 * 445 * @param queryTemplate 446 * @param filterProps 447 * @return the query 448 * @throws PortalException 449 */ 450 private String createQueryFromTemplate( String queryTemplate, RPCParameter[] filterProps ) 451 throws PortalException { 452 453 queryTemplate = getInitParam( queryTemplate ); 454 if ( !( new File( queryTemplate ).isAbsolute() ) ) { 455 queryTemplate = sc.getRealPath( queryTemplate ); 456 } 457 StringBuffer template = new StringBuffer( 10000 ); 458 try { 459 BufferedReader br = new BufferedReader( new FileReader( queryTemplate ) ); 460 String line = null; 461 while ( ( line = br.readLine() ) != null ) { 462 template.append( line ); 463 } 464 br.close(); 465 } catch ( IOException e ) { 466 LOG.logError( e.getMessage(), e ); 467 throw new PortalException( "could not read query template: " + parameter.get( "TEMPLATE" ) ); 468 } 469 String query = template.toString(); 470 if ( filterProps != null ) { 471 for ( int i = 0; i < filterProps.length; i++ ) { 472 RPCStruct struct = (RPCStruct) filterProps[i].getValue(); 473 String name = RPCUtils.getRpcPropertyAsString( struct, "propertyName" ); 474 String value = RPCUtils.getRpcPropertyAsString( struct, "value" ); 475 value = StringTools.replace( value, "XXX", "%", true ); 476 query = StringTools.replace( query, '$' + name, value, true ); 477 } 478 } 479 return query; 480 } 481 482 /** 483 * creates a WFS GetFeature query from a OGC filter expression send from a client 484 * 485 * @return the query 486 * @throws PortalException 487 */ 488 private String createQueryFromFilter( String[] featureTypes, String[] xmlns, String filter ) { 489 StringBuffer query = new StringBuffer( 20000 ); 490 String format = "text/xml; subtype=gml/3.1.1"; 491 int maxFeatures = -1; 492 String resultType = "results"; 493 if ( parameter.get( "OUTPUTFORMAT" ) != null ) { 494 format = parameter.get( "OUTPUTFORMAT" ); 495 } 496 if ( parameter.get( "MAXFEATURE" ) != null ) { 497 maxFeatures = Integer.parseInt( parameter.get( "MAXFEATURE" ) ); 498 } 499 if ( parameter.get( "RESULTTYPE" ) != null ) { 500 resultType = parameter.get( "RESULTTYPE" ); 501 } 502 query.append( "<wfs:GetFeature outputFormat='" ).append( format ); 503 query.append( "' maxFeatures='" ).append( maxFeatures ).append( "' " ); 504 query.append( " resultType='" ).append( resultType ).append( "' " ); 505 for ( int i = 0; i < xmlns.length; i++ ) { 506 String[] tmp = StringTools.toArray( xmlns[i], "=", false ); 507 query.append( "xmlns:" ).append( tmp[0] ).append( "='" ); 508 query.append( tmp[1] ).append( "' " ); 509 } 510 query.append( "xmlns:wfs='http://www.opengis.net/wfs' " ); 511 query.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); 512 query.append( "xmlns:gml='http://www.opengis.net/gml' " ); 513 query.append( ">" ); 514 515 query.append( "<wfs:Query " ); 516 for ( int i = 0; i < featureTypes.length; i++ ) { 517 query.append( "typeName='" ).append( featureTypes[i] ); 518 if ( i < featureTypes.length - 1 ) { 519 query.append( "," ); 520 } 521 } 522 query.append( "'>" ); 523 query.append( filter ); 524 query.append( "</wfs:Query></wfs:GetFeature>" ); 525 526 return query.toString(); 527 } 528 529 /** 530 * creates an OGC FE filter from a set of KVP-encode properties and logical opertaions 531 * 532 * @return the filter 533 */ 534 private String createFilterFromProperties() { 535 String tmp = parameter.get( "FILTERPROPERTIES" ); 536 if ( tmp != null ) { 537 String[] properties = StringTools.extractStrings( tmp, "{", "}" ); 538 String logOp = parameter.get( "LOGICALOPERATOR" ); 539 StringBuffer filter = new StringBuffer( 10000 ); 540 filter.append( "<ogc:Filter>" ); 541 if ( properties.length > 1 ) { 542 filter.append( "<ogc:" ).append( logOp ).append( '>' ); 543 } 544 for ( int i = 0; i < properties.length; i++ ) { 545 String[] prop = StringTools.extractStrings( tmp, "[", "]" ); 546 if ( "!=".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { 547 filter.append( "<ogc:Not>" ); 548 } 549 if ( "=".equals( prop[1] ) || "!=".equals( prop[1] ) ) { 550 filter.append( "<ogc:PropertyIsEqualTo>" ); 551 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 552 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 553 filter.append( "</ogc:PropertyIsEqualTo>" ); 554 } else if ( ">=".equals( prop[1] ) ) { 555 filter.append( "<ogc:PropertyIsGreaterThanOrEqualTo>" ); 556 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 557 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 558 filter.append( "</ogc:PropertyIsGreaterThanOrEqualTo>" ); 559 } else if ( ">".equals( prop[1] ) ) { 560 filter.append( "<ogc:PropertyIsGreaterThan>" ); 561 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 562 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 563 filter.append( "</ogc:PropertyIsGreaterThan>" ); 564 } else if ( "<=".equals( prop[1] ) ) { 565 filter.append( "<ogc:PropertyIsLessThanOrEqualTo>" ); 566 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 567 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 568 filter.append( "</ogc:PropertyIsLessThanOrEqualTo>" ); 569 } else if ( "<".equals( prop[1] ) ) { 570 filter.append( "<ogc:PropertyIsLessThan>" ); 571 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 572 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 573 filter.append( "</ogc:PropertyIsLessThan>" ); 574 } else if ( "LIKE".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { 575 filter.append( "<ogc:PropertyIsLike wildCard='%' singleChar='#' escape='!'>" ); 576 filter.append( "<ogc:PropertyName>" ).append( prop[0] ).append( "</ogc:PropertyName>" ); 577 filter.append( "<ogc:Literal>" ).append( prop[2] ).append( "</ogc:Literal>" ); 578 filter.append( "</ogc:PropertyIsLike>" ); 579 } 580 if ( "!=".equals( prop[1] ) || "NOT LIKE".equals( prop[1] ) ) { 581 filter.append( "</ogc:Not>" ); 582 } 583 } 584 if ( properties.length > 1 ) { 585 filter.append( "</ogc:" ).append( logOp ).append( '>' ); 586 } 587 filter.append( "</ogc:Filter>" ); 588 return filter.toString(); 589 } 590 return ""; 591 } 592 593 }