/**********************************************************************
 *
 * Project:  MapServer
 * Purpose:  Implementation of WFS CONNECTIONTYPE - client to WFS servers
 * Author:   Daniel Morissette, DM Solutions Group (morissette@dmsolutions.ca)
 *
 **********************************************************************
 * Copyright (c) 2002, Daniel Morissette, DM Solutions Group Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 **********************************************************************
 *
 * $Log: mapwfslayer.c,v $
 * Revision 1.29  2004/11/25 06:19:05  dan
 * Add trailing "?" or "&" to connection string when required in WFS
 * client layers using GET method (bug 1082)
 *
 * Revision 1.28  2004/11/23 23:16:29  dan
 * Fixed build error when WFS was not enabled (bug 1083)
 *
 * Revision 1.27  2004/11/16 21:57:49  dan
 * Final pass at updating WMS/WFS client/server interfaces to lookup "ows_*"
 * metadata in addition to default "wms_*"/"wfs_*" metadata (bug 568)
 *
 * Revision 1.26  2004/11/15 20:35:02  dan
 * Added msLayerIsOpen() to all vector layer types (bug 1051)
 *
 * Revision 1.25  2004/11/12 17:05:01  assefa
 * Use %.17g instrad of %f when output bbox values (Bug 678)
 *
 * Revision 1.24  2004/10/21 04:30:57  frank
 * Added standardized headers.  Added MS_CVSID().
 *
 * Revision 1.23  2004/07/13 20:39:37  dan
 * Made msTmpFile() more robust using msBuildPath() to return absolute paths (bug 771)
 *
 * Revision 1.22  2004/06/22 20:55:21  sean
 * Towards resolving issue 737 changed hashTableObj to a structure which contains a hashObj **items.  Changed all hash table access functions to operate on the target table by reference.  msFreeHashTable should not be used on the hashTableObj type members of mapserver structures, use msFreeHashItems instead.
 *
 * Revision 1.21  2004/04/27 16:16:11  assefa
 * Use wfs_request_method instead of wms_request_method.
 *
 * Revision 1.20  2004/03/15 07:43:42  frank
 * avoid warnings about usused variables when building without USE_WFS_LYR
 *
 * Revision 1.19  2004/03/11 22:45:39  dan
 * Added pszPostContentType in httpRequestObj instead of using hardcoded
 * text/html mime type for all post requests.
 *
 * Revision 1.18  2003/10/30 22:37:01  assefa
 * Add function msWFSExecuteGetFeature on a wfs layer.
 *
 * Revision 1.17  2003/10/22 20:20:09  assefa
 * Test if wfs_filter metada is empty.
 *
 * Revision 1.16  2003/09/19 21:54:19  assefa
 * Add support fot the Post request.
 *
 * Revision 1.15  2003/09/10 19:53:19  assefa
 * Use local hash function instead of md5.
 *
 * Revision 1.14  2003/09/10 03:52:53  assefa
 * The <Filter ...> and </Filter> is now generated here intstead of
 * comming from the wfs_filter metadata parameter.
 *
 * Revision 1.13  2003/09/04 17:47:15  assefa
 * Add filterencoding tests.
 *
 * Revision 1.12  2003/03/26 20:24:38  dan
 * Do not call msDebug() unless debug flag is turned on
 *
 * Revision 1.11  2003/02/19 14:19:02  frank
 * cleanup warnings
 *
 * Revision 1.10  2002/12/19 07:35:53  dan
 * Call msWFSLayerWhichShapes() inside open() to force downloading layer and
 * enable msOGRLayerGetItems() to work for queries.  i.e.WFS queries work now.
 *
 * Revision 1.9  2002/12/19 06:30:59  dan
 * Enable caching WMS/WFS request using tmp filename built from URL
 *
 * Revision 1.8  2002/12/19 05:17:09  dan
 * Report WFS exceptions, and do not fail on WFS requests returning 0 features
 *
 * Revision 1.7  2002/12/17 21:33:54  dan
 * Enable following redirections with libcurl (requires libcurl 7.10.1+)
 *
 * Revision 1.6  2002/12/17 05:30:17  dan
 * Fixed HTTP timeout value (in secs, not msecs) for WMS/WFS requests
 *
 * Revision 1.5  2002/12/17 04:48:25  dan
 * Accept version 0.0.14 in addition to 1.0.0
 *
 * Revision 1.4  2002/12/16 20:35:00  dan
 * Flush libwww and use libcurl instead for HTTP requests in WMS/WFS client
 *
 * Revision 1.3  2002/12/13 01:32:53  dan
 * OOOpps.. lp->wfslayerinfo was not set in msWFSLayerOpen()
 *
 * Revision 1.2  2002/12/13 00:57:31  dan
 * Modified WFS implementation to behave more as a real vector data source
 *
 * Revision 1.1  2002/10/09 02:29:03  dan
 * Initial implementation of WFS client support.
 *
 **********************************************************************/

#include "map.h"
#include "maperror.h"
#include "mapows.h"

#include <time.h>

#if defined(_WIN32) && !defined(__CYGWIN__)
#include <process.h>
#endif

MS_CVSID("$Id: mapwfslayer.c,v 1.29 2004/11/25 06:19:05 dan Exp $")

#define WFS_V_0_0_14  14
#define WFS_V_1_0_0  100


/*====================================================================
 *  Private (static) functions
 *====================================================================*/

#ifdef USE_WFS_LYR

/************************************************************************/
/*                           msBuildRequestParms                        */
/*                                                                      */
/*      Build the params object based on the metadata                   */
/*      information. This object will be used when building the Get     */
/*      and Post requsests.                                             */
/*      Note : Verify the connection string to extract some values      */
/*      for backward compatiblity. (It is though depricated).           */
/*      This will also set layer projection and compute BBOX in that    */
/*      projection.                                                     */
/*                                                                      */
/************************************************************************/
static wfsParamsObj *msBuildRequestParams(mapObj *map, layerObj *lp, 
                                          rectObj *bbox_ret)
{
    wfsParamsObj *psParams = NULL;
    rectObj bbox;
    const char *pszTmp; 
    int nLength, i = 0;
    char *pszVersion, *pszService, *pszTypeName = NULL;

    if (!map || !lp || !bbox_ret)
      return NULL;

    if (lp->connection == NULL)
      return NULL;
    
    psParams = msWFSCreateParamsObj();
    pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "version");
    if (pszTmp)
      psParams->pszVersion = strdup(pszTmp);
    else
    {
        pszTmp = strstr(lp->connection, "VERSION=");
        if (!pszTmp)
           pszTmp = strstr(lp->connection, "version=");
        if (pszTmp)
        {
            pszVersion = strchr(pszTmp, '=')+1;
            if (strncmp(pszVersion, "0.0.14", 6) == 0)
              psParams->pszVersion = strdup("0.0.14");
            else if (strncmp(pszVersion, "1.0.0", 5) == 0)
              psParams->pszVersion = strdup("1.0.0");
        }
    }


    pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "service");
    if (pszTmp)
      psParams->pszService = strdup(pszTmp);
    else
    {
        pszTmp = strstr(lp->connection, "SERVICE=");
        if (!pszTmp)
          pszTmp = strstr(lp->connection, "service=");
        if (pszTmp)
        {
            pszService = strchr(pszTmp, '=')+1;
            if (strncmp(pszService, "WFS", 3) == 0)
              psParams->pszService = strdup("WFS");
        }
    }


    pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "typename");
    if (pszTmp)
      psParams->pszTypeName = strdup(pszTmp);
    else
    {
        pszTmp = strstr(lp->connection, "TYPENAME=");
        if (!pszTmp)
          pszTmp = strstr(lp->connection, "typename=");
        if (pszTmp)
        {
            pszTypeName = strchr(pszTmp, '=')+1;
            if (pszTypeName)
            {
                nLength = strlen(pszTypeName);
                if (nLength > 0)
                {
                    for (i=0; i<nLength; i++)
                    {
                        if (pszTypeName[i] == '&')
                          break;
                    }

                    if (i<nLength)
                    {
                        char *pszTypeNameTmp = NULL;
                        pszTypeNameTmp = strdup(pszTypeName);
                        pszTypeNameTmp[i] = '\0';
                        psParams->pszTypeName = strdup(pszTypeNameTmp);
                        free(pszTypeNameTmp);
                    }
                    else
                      psParams->pszTypeName = strdup(pszTypeName);
                }
            }
        }
    }

    pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "filter");
    if (pszTmp && strlen(pszTmp) > 0)
    {
        psParams->pszFilter = malloc(sizeof(char)*(strlen(pszTmp)+17+1));
        sprintf(psParams->pszFilter, "<Filter>%s</Filter>", pszTmp);
        //<Filter xmlns=\"http://www.opengis.net/ogc\" xmlns:gml=\"http://www.opengis.net/gml\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.opengis.net/ogc ../filter/1.0.0/filter.xsd http://www.opengis.net/gml../gml/2.1/geometry.xsd\">
    }

     pszTmp = msOWSLookupMetadata(&(lp->metadata), "FO", "maxfeatures");
     if (pszTmp)
       psParams->nMaxFeatures = atoi(pszTmp);

    //Request is always GetFeature;
    psParams->pszRequest = strdup("GetFeature");

               
/* ------------------------------------------------------------------
 * Figure the SRS we'll use for the request.
 * - Fetch the map SRS (if it's EPSG)
 * - Check if map SRS is listed in layer wfs_srs metadata
 * - If map SRS is valid for this layer then use it
 * - Otherwise request layer in its default SRS and we'll reproject later
 * ------------------------------------------------------------------ */

// __TODO__ WFS servers support only one SRS... need to decide how we'll
// handle this and document it well.
// It's likely that we'll simply reproject the BBOX to teh layer's projection.

/* ------------------------------------------------------------------
 * Set layer SRS and reproject map extents to the layer's SRS
 * ------------------------------------------------------------------ */
#ifdef __TODO__
    // No need to set lp->proj if it's already set to the right EPSG code
    if ((pszTmp = msGetEPSGProj(&(lp->projection), NULL, MS_TRUE)) == NULL ||
        strcasecmp(pszEPSG, pszTmp) != 0)
    {
        char szProj[20];
        sprintf(szProj, "init=epsg:%s", pszEPSG+5);
        if (msLoadProjectionString(&(lp->projection), szProj) != 0)
            return NULL;
    }
#endif

    bbox = map->extent;
    if (msProjectionsDiffer(&(map->projection), &(lp->projection)))
    {
        msProjectRect(&(map->projection), &(lp->projection), &bbox);
    }

    if (bbox_ret != NULL)
      *bbox_ret = bbox;

    return psParams;

}

/**********************************************************************
 *                          msBuildWFSLayerPostRequest()
 *
 * Build a WFS GetFeature xml document for a Post Request.  
 *
 * Returns a reference to a newly allocated string that should be freed 
 * by the caller.
 **********************************************************************/
static char *msBuildWFSLayerPostRequest(mapObj *map, layerObj *lp, 
                                        rectObj *bbox, wfsParamsObj *psParams) 
{
    char *pszPostReq = NULL;
    char *pszFilter = NULL;

    if (psParams->pszVersion == NULL || 
        (strncmp(psParams->pszVersion, "0.0.14", 6) != 0 &&
        strncmp(psParams->pszVersion, "1.0.0", 5)) != 0)
    {
        msSetError(MS_WFSCONNERR, "MapServer supports only WFS 1.0.0 or 0.0.14 (please verify the version metadata wfs_version).", "msBuildWFSLayerPostRequest()");
        return NULL;
    }

    if (psParams->pszService == NULL ||
        strncmp(psParams->pszService, "WFS", 3) != 0)
    {
        msSetError(MS_WFSCONNERR, "Metadata wfs_service must be set in the layer", "msBuildWFSLayerPostRequest()");
        return NULL;
    }

    if (psParams->pszTypeName == NULL)
    {
        msSetError(MS_WFSCONNERR, "Metadata wfs_typename must be set in the layer", "msBuildWFSLayerPostRequest()");
        return NULL;
    } 
    
    

    if (psParams->pszFilter)
      pszFilter = psParams->pszFilter;
    else
    {
        pszFilter = (char *)malloc(sizeof(char)*500);
        sprintf(pszFilter, "<Filter>\n"
"<BBOX>\n"
"<PropertyName>Geometry</PropertyName>\n"
"<Box>\n"
"<coordinates>%lf,%lf %lf,%lf</coordinates>\n"
"</Box>\n"
"</BBOX>\n"
"</Filter>",bbox->minx, bbox->miny, bbox->maxx, bbox->maxy);
    }

    pszPostReq = (char *)malloc(sizeof(char)*(strlen(pszFilter)+500));
    if (psParams->nMaxFeatures > 0)
      sprintf(pszPostReq, "<?xml version=\"1.0\" ?>\n"
"<GetFeature\n"
"service=\"WFS\"\n"
"version=\"1.0.0\"\n"
"maxFeatures=\"%d\"\n"
"outputFormat=\"GML2\">\n"
"<Query typeName=\"%s\">\n"
"%s"
"</Query>\n"
"</GetFeature>\n", psParams->nMaxFeatures, psParams->pszTypeName, pszFilter);
    else
      sprintf(pszPostReq, "<?xml version=\"1.0\" ?>\n"
"<GetFeature\n"
"service=\"WFS\"\n"
"version=\"1.0.0\"\n"
"outputFormat=\"GML2\">\n"
"<Query typeName=\"%s\">\n"
"%s"
"</Query>\n"
"</GetFeature>\n", psParams->pszTypeName, pszFilter);
    if (psParams->pszFilter == NULL)
      free(pszFilter);


    return pszPostReq; 
}

/**********************************************************************
 *                          msBuildWFSLayerGetURL()
 *
 * Build a WFS GetFeature URL for a Get Request.  
 *
 * Returns a reference to a newly allocated string that should be freed 
 * by the caller.
 **********************************************************************/
static char *msBuildWFSLayerGetURL(mapObj *map, layerObj *lp, rectObj *bbox,
                                   wfsParamsObj *psParams) 
{
    char *pszURL = NULL, *pszOnlineResource=NULL;
    const char *pszTmp; 
    char *pszVersion, *pszService, *pszTypename = NULL;
    int bVersionInConnection = 0, bServiceInConnection = 0;
    int bTypenameInConnection = 0;
    

    if (lp->connectiontype != MS_WFS || lp->connection == NULL)
    {
        msSetError(MS_WFSCONNERR, "Call supported only for CONNECTIONTYPE WFS",
                   "msBuildWFSLayerGetURL()");
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Find out request version. Look first for the wfs_version        */
/*      metedata. If not available try to find out if the CONNECTION    */
/*      string contains the version. This last test is done for         */
/*      backward compatiblity but is depericated.                       */
/* -------------------------------------------------------------------- */
    pszVersion = psParams->pszVersion;
    if (!pszVersion)
    {
      if ((pszTmp = strstr(lp->connection, "VERSION=")) == NULL &&
          (pszTmp = strstr(lp->connection, "version=")) == NULL )
      {
        msSetError(MS_WFSCONNERR, "Metadata wfs_version must be set in the layer", "msBuildWFSLayerGetURL()");
        return NULL;
      }
      pszVersion = strchr(pszTmp, '=')+1;
      bVersionInConnection = 1;
    }
    
   
    if (strncmp(pszVersion, "0.0.14", 6) != 0 &&
        strncmp(pszVersion, "1.0.0", 5) != 0 )
    {
        msSetError(MS_WFSCONNERR, "MapServer supports only WFS 1.0.0 or 0.0.14 (please verify the version metadata wfs_version).", "msBuildWFSLayerGetURL()");
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Find out the service. Look first for the wfs_service            */
/*      metadata. If not available try to find out if the CONNECTION    */
/*      string contains it. This last test is done for                  */
/*      backward compatiblity but is depericated.                       */
/* -------------------------------------------------------------------- */
    pszService = psParams->pszService;
    if (!pszService)
    {
      if ((pszTmp = strstr(lp->connection, "SERVICE=")) == NULL &&
          (pszTmp = strstr(lp->connection, "service=")) == NULL )
      {
        msSetError(MS_WFSCONNERR, "Metadata wfs_service must be set in the layare", "msBuildWFSLayerGetURL()");
        return NULL;
      }
      pszService = strchr(pszTmp, '=')+1;
      bServiceInConnection = 1;
    }
    
   
    if (strncmp(pszService, "WFS", 3))
    {
        msSetError(MS_WFSCONNERR, "MapServer supports only WFS as a SERVICE (pease verify the service metadata wfs_service).", "msBuildWFSLayerGetURL()");
        return NULL;
    }
    pszService = strdup("WFS");

/* -------------------------------------------------------------------- */
/*      Find out the typename. Look first for the wfs_tyename           */
/*      metadata. If not available try to find out if the CONNECTION    */
/*      string contains it. This last test is done for                  */
/*      backward compatiblity but is depericated.                       */
/* -------------------------------------------------------------------- */
    pszTypename = psParams->pszTypeName;
    if (!pszTypename)
    {
      if ((pszTmp = strstr(lp->connection, "TYPENAME=")) == NULL &&
          (pszTmp = strstr(lp->connection, "typename=")) == NULL )
      {
        msSetError(MS_WFSCONNERR, "Metadata wfs_typename must be set in the layare", "msBuildWFSLayerGetURL()");
        return NULL;
      }
      bTypenameInConnection = 1;
    }
    

/* -------------------------------------------------------------------- 
 * Build the request URL.
 * At this point we set only the following parameters for GetFeature:
 *   REQUEST
 *   BBOX
 *   VERSION
 *   SERVICE
 *   TYPENAME
 *   FILTER
 *   MAXFEATURES
 *
 * For backward compatiblity the user could also have in the connection
 * string the following parameters (but it is depricated):
 *   VERSION
 *   SERVICE
 *   TYPENAME
 * -------------------------------------------------------------------- */
    // Make sure we have a big enough buffer for the URL
    if(!(pszURL = (char *)malloc((strlen(lp->connection)+1024)*sizeof(char)))) 
    {
        msSetError(MS_MEMERR, NULL, "msBuildWFSLayerGetURL()");
        return NULL;
    }

    // __TODO__ We have to urlencode each value... especially the BBOX values
    // because if they end up in exponent format (123e+06) the + will be seen
    // as a space by the remote server.

/* -------------------------------------------------------------------- */
/*      build the URL,                                                  */
/* -------------------------------------------------------------------- */
    // make sure connection ends with "&" or "?"
    pszOnlineResource = msOWSTerminateOnlineResource(lp->connection);
    sprintf(pszURL, "%s", pszOnlineResource);
    msFree(pszOnlineResource);

    //REQUEST
    sprintf(pszURL + strlen(pszURL),  "&REQUEST=GetFeature");

    //VERSION
    if (!bVersionInConnection)
      sprintf(pszURL + strlen(pszURL),  "&VERSION=%s", pszVersion);
    
    //SERVICE
    if (!bServiceInConnection)
        sprintf(pszURL + strlen(pszURL),  "&SERVICE=%s", pszService);

    //TYPENAME
    if (!bTypenameInConnection)
      sprintf(pszURL + strlen(pszURL),  "&TYPENAME=%s", pszTypename);

/* -------------------------------------------------------------------- */
/*      If the filter parameter is given in the wfs_filter metadata,    */
/*      we use it and do not send the BBOX paramter as they are         */
/*      mutually exclusive.                                             */
/* -------------------------------------------------------------------- */
    if (psParams->pszFilter)
    {   
        sprintf(pszURL + strlen(pszURL), "&FILTER=%s",
                msEncodeUrl(psParams->pszFilter));
    }
    else
      sprintf(pszURL + strlen(pszURL), 
              "&BBOX=%.17g,%.17g,%.17g,%.17g",
              bbox->minx, bbox->miny, bbox->maxx, bbox->maxy);
    
    if (psParams->nMaxFeatures > 0)
      sprintf(pszURL + strlen(pszURL),  "&MAXFEATURES=%d", psParams->nMaxFeatures);

    return pszURL;

}

/**********************************************************************
 *                          msWFSLayerInfo
 *
 **********************************************************************/
typedef struct ms_wfs_layer_info_t
{
    char        *pszGMLFilename;
    rectObj     rect;                     /* set by WhichShapes */
    char        *pszGetUrl;
    int         nStatus;           /* HTTP status */
    int         bLayerOpened;      /* False until msWFSLayerOpen() is called*/
} msWFSLayerInfo;


/**********************************************************************
 *                          msAllocWFSLayerInfo()
 *
 **********************************************************************/
static msWFSLayerInfo *msAllocWFSLayerInfo()
{
    msWFSLayerInfo *psInfo;

    psInfo = (msWFSLayerInfo*)calloc(1,sizeof(msWFSLayerInfo));
    if (psInfo)
    {
        psInfo->pszGMLFilename = NULL;
        psInfo->rect.minx = psInfo->rect.maxx = 0;
        psInfo->rect.miny = psInfo->rect.maxy = 0;
        psInfo->pszGetUrl = NULL;
        psInfo->nStatus = 0;
        psInfo->bLayerOpened = MS_FALSE;
    }
    else
    {
        msSetError(MS_MEMERR, NULL, "msAllocWFSLayerInfo()");
    }

    return psInfo;
}

/**********************************************************************
 *                          msFreeWFSLayerInfo()
 *
 **********************************************************************/
static void msFreeWFSLayerInfo(msWFSLayerInfo *psInfo)
{
    if (psInfo)
    {
        if (psInfo->pszGMLFilename)
            free(psInfo->pszGMLFilename);
        if (psInfo->pszGetUrl)
            free(psInfo->pszGetUrl);

        free(psInfo);
    }
}

#endif /* USE_WFS_LYR */

/*====================================================================
 *  Public functions
 *====================================================================*/

/**********************************************************************
 *                          msPrepareWFSLayerRequest()
 *
 **********************************************************************/

int msPrepareWFSLayerRequest(int nLayerId, mapObj *map, layerObj *lp,
                             httpRequestObj *pasReqInfo, int *numRequests) 
{
#ifdef USE_WFS_LYR
    char *pszURL = NULL;
    const char *pszTmp;
    rectObj bbox;
    int nTimeout;
    int nStatus = MS_SUCCESS;
    msWFSLayerInfo *psInfo = NULL;
    char *pszHashFileName = NULL;
    int bPostRequest = 0;
    wfsParamsObj *psParams = NULL;
    

    if (lp->connectiontype != MS_WFS || lp->connection == NULL)
        return MS_FAILURE;

/* ------------------------------------------------------------------
 * Build a params object that will be used by to build the request, 
  this will also set layer projection and compute BBOX in that projection.
 * ------------------------------------------------------------------ */
    psParams = msBuildRequestParams(map, lp, &bbox);
    if (!psParams)
      return MS_FAILURE;

/* -------------------------------------------------------------------- */
/*      Depending on the metadata wfs_request_method, build a Get or    */
/*      a Post URL.                                                     */
/*    If it is a Get request the URL would contain all the parameters in*/
/*      the string;                                                     */
/*      If it is a Post request, the URL will only contain the          */
/*      connection string comming from the layer.                       */
/* -------------------------------------------------------------------- */
    if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), 
                                      "FO", "request_method")) != NULL)
    {
        if (strncmp(pszTmp, "GET", 3) ==0)
        {
            pszURL = msBuildWFSLayerGetURL(map, lp, &bbox, psParams);
            if (!pszURL)
            {
              /* an error was already reported. */
                return MS_FAILURE;
            }
        }
    }
    //else it is a post request and just get the connection string
    if (!pszURL)
    {
        bPostRequest = 1;
        pszURL = strdup(lp->connection);
    }

    
/* ------------------------------------------------------------------
 * check to see if a the metadata wfs_connectiontimeout is set. If it is 
 * the case we will use it, else we use the default which is 30 seconds.
 * First check the metadata in the layer object and then in the map object.
 * ------------------------------------------------------------------ */
    nTimeout = 30;  // Default is 30 seconds 
    if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), 
                                      "FO", "connectiontimeout")) != NULL)
    {
        nTimeout = atoi(pszTmp);
    }
    else if ((pszTmp = msOWSLookupMetadata(&(map->web.metadata), 
                                           "FO", "connectiontimeout")) != NULL)
    {
        nTimeout = atoi(pszTmp);
    }

/* ------------------------------------------------------------------
 * If nLayerId == -1 then we need to figure it
 * ------------------------------------------------------------------ */
    if (nLayerId == -1)
    {
        int iLayer;
        for(iLayer=0; iLayer < map->numlayers; iLayer++)
        {
            if (&(map->layers[iLayer]) == lp)
            {
                nLayerId = iLayer;
                break;
            }
        }
    }

/* ------------------------------------------------------------------
 * Add a request to the array (already preallocated)
 * ------------------------------------------------------------------ */
    pasReqInfo[(*numRequests)].nLayerId = nLayerId;
    pasReqInfo[(*numRequests)].pszGetUrl = pszURL;

    if (bPostRequest)
    {
        pasReqInfo[(*numRequests)].pszPostRequest = 
            msBuildWFSLayerPostRequest(map, lp, &bbox, psParams);
        pasReqInfo[(*numRequests)].pszPostContentType =
            strdup("text/xml");
    }

    // We'll store the remote server's response to a tmp file.
    if (bPostRequest)
    {
        char *pszPostTmpName = NULL;
        pszPostTmpName = (char *)malloc(sizeof(char)*(strlen(pszURL)+128));
        sprintf(pszPostTmpName,"%s%ld%d",
                pszURL, (long)time(NULL), (int)getpid());
        pszHashFileName = msHashString(pszPostTmpName);
        free(pszPostTmpName);
    }
    else
      pszHashFileName = msHashString(pszURL);
    pszURL = NULL;
    
    pasReqInfo[(*numRequests)].pszOutputFile =  
        msOWSBuildURLFilename(map->web.imagepath, 
                              pszHashFileName,".tmp.gml");
    free(pszHashFileName);
    pasReqInfo[(*numRequests)].nStatus = 0;
    pasReqInfo[(*numRequests)].nTimeout = nTimeout;
    pasReqInfo[(*numRequests)].bbox = bbox;
    pasReqInfo[(*numRequests)].debug = lp->debug;

/* ------------------------------------------------------------------
 * Pre-Open the layer now, (i.e. alloc and fill msWFSLayerInfo inside 
 * layer obj).  Layer will be ready for use when the main mapserver 
 * code calls msLayerOpen().
 * ------------------------------------------------------------------ */
    if (lp->wfslayerinfo != NULL)
    {
        psInfo =(msWFSLayerInfo*)(lp->wfslayerinfo);
    }
    else
    {
        lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo();
    }

    if (psInfo->pszGMLFilename) 
        free(psInfo->pszGMLFilename);
    psInfo->pszGMLFilename=strdup(pasReqInfo[(*numRequests)].pszOutputFile);
 
    psInfo->rect = pasReqInfo[(*numRequests)].bbox;

    if (psInfo->pszGetUrl) 
        free(psInfo->pszGetUrl);
    psInfo->pszGetUrl = strdup(pasReqInfo[(*numRequests)].pszGetUrl);

    psInfo->nStatus = 0;

    (*numRequests)++;

    if (psParams)
    {
        msWFSFreeParamsObj(psParams);
        psParams = NULL;
    }
    return nStatus;

#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msPrepareWFSLayerRequest");
  return(MS_FAILURE);

#endif /* USE_WFS_LYR */

}

/**********************************************************************
 *                          msWFSUpdateRequestInfo()
 *
 * This function is called after a WFS request has been completed so that
 * we can copy request result information from the httpRequestObj to the
 * msWFSLayerInfo struct.  Things to copy here are the HTTP status, exceptions
 * information, mime type, etc.
 **********************************************************************/
void msWFSUpdateRequestInfo(layerObj *lp, httpRequestObj *pasReqInfo)
{
#ifdef USE_WFS_LYR
    if (lp->wfslayerinfo)
    {
        msWFSLayerInfo *psInfo = NULL;

        psInfo =(msWFSLayerInfo*)(lp->wfslayerinfo);

        // Copy request results infos to msWFSLayerInfo struct
        // For now there is only nStatus, but we should eventually add
        // mime type and WFS exceptions information.
        psInfo->nStatus = pasReqInfo->nStatus;
    }
#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msWFSUpdateRequestInfo()");
#endif /* USE_WFS_LYR */
}

/**********************************************************************
 *                          msWFSLayerOpen()
 *
 * WFS layers are just a special case of OGR connection.  Only the open/close
 * methods differ since they have to download and maintain GML files in cache
 * but the rest is mapped directly to OGR function calls in maplayer.c
 *
 **********************************************************************/

int msWFSLayerOpen(layerObj *lp, 
                   const char *pszGMLFilename, rectObj *defaultBBOX) 
{
#ifdef USE_WFS_LYR
    int status = MS_SUCCESS;
    msWFSLayerInfo *psInfo = NULL;

    if (lp->wfslayerinfo != NULL)
    {
        psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;

        // Layer already opened.  If explicit filename requested then check
        // that file was already opened with the same filename.
        // If no explicit filename requested then we'll try to reuse the
        // previously opened layer... this will happen in a msDrawMap() call.
        if (pszGMLFilename == NULL ||
            (psInfo->pszGMLFilename && pszGMLFilename && 
             strcmp(psInfo->pszGMLFilename, pszGMLFilename) == 0) )
        {
            return MS_SUCCESS;  // Nothing to do... layer is already opened
        }
        else
        {
            // Hmmm... should we produce a fatal error?
            // For now we'll just close the layer and reopen it.
            if (lp->debug)
                msDebug("msWFSLayerOpen(): Layer already opened (%s)\n",
                        lp->name?lp->name:"(null)" );
            msWFSLayerClose(lp);
        }
    }

/* ------------------------------------------------------------------
 * Alloc and fill msWFSLayerInfo inside layer obj
 * ------------------------------------------------------------------ */
    lp->wfslayerinfo = psInfo = msAllocWFSLayerInfo();

    if (pszGMLFilename)
        psInfo->pszGMLFilename = strdup(pszGMLFilename);
    else
    {
        if (lp->map->web.imagepath==NULL || strlen(lp->map->web.imagepath)==0)
        {
            msSetError(MS_WFSERR, 
                  "WEB.IMAGEPATH must be set to use WFS client connections.",
                       "msPrepareWMSLayerRequest()");
            return MS_FAILURE;
        }

        psInfo->pszGMLFilename = msTmpFile(lp->map->mappath, 
                                           lp->map->web.imagepath, 
                                           "tmp.gml");
    }

    if (defaultBBOX)
    {
        // __TODO__ If new bbox differs from current one then we should
        // invalidate current GML file in cache
        psInfo->rect = *defaultBBOX;
    }
    else
    {
        // Use map bbox by default
        psInfo->rect = lp->map->extent;
    }

    // We will call whichshapes() now and force downloading layer right
    // away.  This saves from having to call DescribeFeatureType and
    // parsing the response (being lazy I guess) and anyway given the
    // way we work with layers right now the bbox is unlikely to change
    // between now and the time whichshapes() would have been called by
    // the MapServer core.
    if (msWFSLayerWhichShapes(lp, psInfo->rect) == MS_FAILURE)
        status = MS_FAILURE;
    
    psInfo->bLayerOpened = MS_TRUE;

    return status;
#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msWFSLayerOpen()");
  return(MS_FAILURE);

#endif /* USE_WFS_LYR */
}

/**********************************************************************
 *                          msWFSLayerIsOpen()
 *
 * Returns MS_TRUE if layer is already open, MS_FALSE otherwise.
 *
 **********************************************************************/

int msWFSLayerIsOpen(layerObj *lp) 
{
#ifdef USE_WFS_LYR
    if (lp->wfslayerinfo != NULL)
        return MS_TRUE;

    return MS_FALSE;
#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msWFSLayerIsOpen()");
  return(MS_FALSE);

#endif /* USE_WFS_LYR */
}

/**********************************************************************
 *                          msWFSLayerInitItemInfo()
 *
 **********************************************************************/

int msWFSLayerInitItemInfo(layerObj *layer)
{
    // Nothing to do here.  OGR will do its own initialization when it
    // opens the actual file.
    // Note that we didn't implement our own msWFSLayerFreeItemInfo()
    // so that the OGR one gets called.
    return MS_SUCCESS;
}

/**********************************************************************
 *                          msWFSLayerGetItems()
 *
 **********************************************************************/

int msWFSLayerGetItems(layerObj *layer)
{
    // For now this method simply lets OGR parse the GML and figure the 
    // schema itself.
    // It could also be implemented to call DescribeFeatureType for
    // this layer, but we don't need to do it so why waste resources?

    return msOGRLayerGetItems(layer);
}

/**********************************************************************
 *                          msWFSLayerWhichShapes()
 *
 **********************************************************************/

int msWFSLayerWhichShapes(layerObj *lp, rectObj rect)
{
#ifdef USE_WFS_LYR
    msWFSLayerInfo *psInfo;
    int status = MS_SUCCESS;
    const char *pszTmp;
    FILE *fp;

    psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;

    if (psInfo == NULL)
    {
        msSetError(MS_WFSCONNERR, "Assertion failed: WFS layer not opened!!!", 
                   "msWFSLayerWhichShapes()");
        return(MS_FAILURE);
    }

/* ------------------------------------------------------------------
 * Check if layer overlaps current view window (using wfs_latlonboundingbox)
 * ------------------------------------------------------------------ */
    if ((pszTmp = msOWSLookupMetadata(&(lp->metadata), 
                                      "FO", "latlonboundingbox")) != NULL)
    {
        char **tokens;
        int n;
        rectObj ext;

        tokens = split(pszTmp, ' ', &n);
        if (tokens==NULL || n != 4) {
            msSetError(MS_WFSCONNERR, "Wrong number of values in 'wfs_latlonboundingbox' metadata.",
                       "msWFSLayerWhichShapes()");
            return MS_FAILURE;
        }

        ext.minx = atof(tokens[0]);
        ext.miny = atof(tokens[1]);
        ext.maxx = atof(tokens[2]);
        ext.maxy = atof(tokens[3]);

        msFreeCharArray(tokens, n);

        // Reproject latlonboundingbox to the selected SRS for the layer and
        // check if it overlaps the bbox that we calculated for the request

        msProjectRect(&(lp->map->latlon), &(lp->projection), &ext);
        if (!msRectOverlap(&rect, &ext))
        {
            // No overlap... nothing to do
            return MS_DONE;  // No overlap.
        }
    }


 /* ------------------------------------------------------------------
  * __TODO__ If new bbox differs from current one then we should
  * invalidate current GML file in cache
  * ------------------------------------------------------------------ */
    psInfo->rect = rect;


/* ------------------------------------------------------------------
 * If file not downloaded yet then do it now.
 * ------------------------------------------------------------------ */
    if (psInfo->nStatus == 0)
    {
        httpRequestObj asReqInfo[2];
        int numReq = 0;

        msHTTPInitRequestObj(asReqInfo, 2);

        if ( msPrepareWFSLayerRequest(-1, lp->map, lp,
                                      asReqInfo, &numReq) == MS_FAILURE  ||
             msOWSExecuteRequests(asReqInfo, numReq, 
                                  lp->map, MS_TRUE) == MS_FAILURE )
        {
            // Delete tmp file... we don't want it to stick around.
            unlink(asReqInfo[0].pszOutputFile);
            return MS_FAILURE;
        }

        // Cleanup
        msHTTPFreeRequestObj(asReqInfo, numReq);

    }

    if ( !MS_HTTP_SUCCESS( psInfo->nStatus ) )
    {
        // Delete tmp file... we don't want it to stick around.
        unlink(psInfo->pszGMLFilename);

        msSetError(MS_WFSCONNERR, 
                   "Got HTTP status %d downloading WFS layer %s", 
                   "msWFSLayerWhichShapes()",
                   psInfo->nStatus, lp->name?lp->name:"(null)");
        return(MS_FAILURE);
    }

/* ------------------------------------------------------------------
 * Check that file is really GML... it could be an exception, or just junk.
 * ------------------------------------------------------------------ */
    if ((fp = fopen(psInfo->pszGMLFilename, "r")) != NULL)
    {
        char szHeader[2000];
        int  nBytes = 0;

        nBytes = fread( szHeader, 1, sizeof(szHeader)-1, fp );
        fclose(fp);

        if (nBytes < 0)
            nBytes = 0;
        szHeader[nBytes] = '\0';

        if ( nBytes == 0 )
        {
            msSetError(MS_WFSCONNERR, 
                       "WFS request produced no oputput for layer %s.",
                       "msWFSLayerWhichShapes()",
                       lp->name?lp->name:"(null)");
            return(MS_FAILURE);

        }
        if ( strstr(szHeader, "<WFS_Exception>") ||
             strstr(szHeader, "<ServiceExceptionReport>") )
        {
            msOWSProcessException(lp, psInfo->pszGMLFilename,
                                  MS_WFSCONNERR, "msWFSLayerWhichShapes()" );
            return MS_FAILURE;
        }
        else if ( strstr(szHeader,"opengis.net/gml") &&
                  strstr(szHeader,"featureMember>") == NULL )
        {
            // This looks like valid GML, but contains 0 features.
            return MS_DONE;
        }
        else if ( strstr(szHeader,"opengis.net/gml") == NULL ||
                  strstr(szHeader,"featureMember>") == NULL )
        {
            // This is probably just junk.
            msSetError(MS_WFSCONNERR, 
                       "WFS request produced unexpected output (junk?) for layer %s.",
                       "msWFSLayerWhichShapes()",
                       lp->name?lp->name:"(null)");
            return(MS_FAILURE);
        }
        
        // If we got this far, it must be a valid GML dataset... keep going
    }


/* ------------------------------------------------------------------
 * Open GML file using OGR.
 * ------------------------------------------------------------------ */
    if ((status = msOGRLayerOpen(lp, psInfo->pszGMLFilename)) != MS_SUCCESS)
        return status;
    
    status = msOGRLayerWhichShapes(lp, rect);

    return status;
#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msWFSLayerWhichShapes()");
  return(MS_FAILURE);

#endif /* USE_WFS_LYR */
}



/**********************************************************************
 *                          msWFSLayerClose()
 *
 **********************************************************************/

int msWFSLayerClose(layerObj *lp)
{
#ifdef USE_WFS_LYR

/* ------------------------------------------------------------------
 * Cleanup OGR connection
 * ------------------------------------------------------------------ */
    if (lp->ogrlayerinfo)
        msOGRLayerClose(lp);

/* ------------------------------------------------------------------
 * Cleanup WFS connection info.
 * __TODO__ For now we flush everything, but we should try to cache some stuff
 * ------------------------------------------------------------------ */
    // __TODO__ unlink()  .gml file and OGR's schema file if they exist
    // unlink(

    msFreeWFSLayerInfo(lp->wfslayerinfo);
    lp->wfslayerinfo = NULL;

    return MS_SUCCESS;

#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msWFSLayerClose()");
  return(MS_FAILURE);

#endif /* USE_WFS_LYR */

}

/**********************************************************************
 *                          msWFSExecuteGetFeature()
 * Returns the temporary gml file name. User shpuld free the return string.
 **********************************************************************/
char *msWFSExecuteGetFeature(layerObj *lp)
{
#ifdef USE_WFS_LYR
  char *gmltmpfile = NULL;
  msWFSLayerInfo *psInfo = NULL;

  if (lp == NULL || lp->connectiontype != MS_WFS)
    return NULL;

  msWFSLayerOpen(lp, NULL, NULL);
  psInfo =(msWFSLayerInfo*)lp->wfslayerinfo;
  if (psInfo &&  psInfo->pszGMLFilename)
    gmltmpfile = strdup(psInfo->pszGMLFilename);
  msWFSLayerClose(lp);

  return gmltmpfile;

#else
/* ------------------------------------------------------------------
 * WFS CONNECTION Support not included...
 * ------------------------------------------------------------------ */
  msSetError(MS_WFSCONNERR, "WFS CLIENT CONNECTION support is not available.", 
             "msExecuteWFSGetFeature()");
  return NULL;

#endif /* USE_WFS_LYR */

}

