    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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    }