.. index:: 
   pair: WxS Services; Mapscript wrappers

.. _mapscript_ows:

*****************************************************************************
 MapScript Wrappers for WxS Services
*****************************************************************************

:Author: Frank Warmerdam
:Contact: warmerdam at pobox.com

.. contents::
    :depth: 2
    :backlinks: top


=============================================================================
 Introduction
=============================================================================

With the implementation of MapServer :ref:`rfc16` in MapServer 4.9, MapScript
now has the ability to invoke MapServer's ability to execute OGC Web Service
requests such as WMS, WCS, and WFS as well as capturing the results of
processing the requests.

This makes it possible to dynamically configure a map object based on 
information in the original request, and to capture the output of processing
requests for further post-processing.  

.. warning::

    :ref:`cgi_controls` and :ref:`runsub` are **not** applied when using
    mapscript WxS wrappers. It is up to the mapscript code you are writing to
    apply any mapfile modifications.

=============================================================================
 Python Examples
=============================================================================

The following trivial example, in Python, demonstrates a script that 
internally provides the map name, but otherwise uses normal mapserver
processing.

.. code-block:: python

  import mapscript

  req = mapscript.OWSRequest()
  req.loadParams()

  map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )
  map.OWSDispatch( req )

The OWSRequest object is used to manage a parsed list of OWS processing 
options.  In the above example they are loaded from the environment using
the loadParams() call which fetches and parses them from QUERY_STRING in the 
same way the :ref:`mapserv` executable would.  

Then we load a map, and invoke OWSDispatch with the given arguments on that
map.  By default the results of the dispatched request are written to stdout
which returns them back to the client. 

The following example ignores all passed in arguments, and manually constructs
a request argument by argument.  It is likely more useful for testing purposes
than for deploying WxS services, but demonstrates direct manipulation of the
request object. 

.. code-block:: python

  import mapscript

  req = mapscript.OWSRequest()
  req.setParameter( 'SERVICE', 'WMS' )
  req.setParameter( 'VERSION', '1.1.0' )
  req.setParameter( 'REQUEST', 'GetCapabilities' )

  map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )
  map.OWSDispatch( req )

The previous example have all let results be returned directly to the client.
But in some cases we want to be able to capture, and perhaps modify the results
of our requests in some custom way.  In the following example we force the 
hated OGC required mime type for errors to simple text/xml (warning - 
non-standard!)

.. code-block:: python

  import mapscript

  req = mapscript.OWSRequest()
  req.loadParams()

  map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )

  mapscript.msIO_installStdoutToBuffer()
  map.OWSDispatch( req )

  content_type = mapscript.msIO_stripStdoutBufferContentType()
  content = mapscript.msIO_getStdoutBufferBytes()

  if content_type == 'vnd.ogc.se_xml':
    content_type = 'text/xml'

  print 'Content-type: ' + content_type
  print
  print content

This example demonstrates capture capturing output of OWSRequest to 
a buffer, capturing the "Content-type:" header value, and capturing the
actual content as binary data.  The msIO_getStdoutBufferBytes() function
returns the stdout buffer as a byte array.  If the result was known to 
be text, the msIO_getStdoutBufferString() function could have been used to 
fetch it as a string instead, for easier text manipulation.

=============================================================================
 Perl Example
=============================================================================

Most of the same capabilities are accessible in all SWIG based mapscript
languages.  In perl, we could script creation of a request like this:

.. code-block:: perl

  #!/usr/bin/perl

  use mapscript;

  $req = new mapscript::OWSRequest();
  $req->setParameter( "SERVICE", "WMS" );
  $req->setParameter( "VERSION", "1.1.0" );
  $req->setParameter( "REQUEST", "GetCapabilities" );

  $map = new mapscript::mapObj( "/u/www/maps/ukpoly/ukpoly.map" );

  mapscript::msIO_installStdoutToBuffer();

  $dispatch_out = $map->OWSDispatch( $req );

  printf "%s\n", mapscript::msIO_getStdoutBufferString();

One issue in Perl is that there is currently no wrapping for binary buffers
so you cannot call msIO_getStdoutBufferBytes(), and so cannot manipulate
binary results.  

More Perl example code
----------------------

.. code-block:: perl

    #!/usr/bin/perl
    ############################################################################
    #
    # Name:     wxs.pl
    # Project:  MapServer
    # Purpose:  MapScript WxS example
    #           
    # Author:   Tom Kralidis
    #
    ##############################################################################
    #
    # Copyright (c) 2007, Tom Kralidis
    #
    # 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 of this Software or works derived from this 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.
    ############################################################################/

    use CGI::Carp qw(fatalsToBrowser);
    use mapscript;
    use strict;
    use warnings;
    use XML::LibXSLT;
    use XML::LibXML;

    my $dispatch;

    # uber-trivial XSLT document, as a file
    my $xsltfile = "/tmp/foo.xslt";

    # here's the actual document inline for
    # testing save and alter $xsltFile above

    =comment
    <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
     xmlns:wfs="http://www.opengis.net/wfs">
     <xsl:output method="xml" indent="yes"/>
     <xsl:template match="/">
      <WFSLayers>
       <xsl:for-each select="//wfs:FeatureType">
        <wfs_layer>
         <name><xsl:value-of select="wfs:Name"/></name>
         <title><xsl:value-of select="wfs:Title"/></title>
        </wfs_layer>
       </xsl:for-each>
      </WFSLayers>
     </xsl:template>
    </xsl:stylesheet>
    =cut

    my $mapfile  = "/tmp/config.map";
    # init OWSRequest object
    my $req = new mapscript::OWSRequest();

    # pick up CGI parameters passed
    $req->loadParams();

    # init mapfile
    my $map = new mapscript::mapObj($mapfile);

    # if this is a WFS GetCapabilities request, then intercept
    # what is normally returned, process with an XSLT document
    # and then return that to the client
    if ($req->getValueByName('REQUEST') eq "GetCapabilities" && $req->getValueByName('SERVICE') eq "WFS") {

      # push STDOUT to a buffer and run the incoming request
      my $io = mapscript::msIO_installStdoutToBuffer();
      $dispatch = $map->OWSDispatch($req);

      # at this point, the client's request is sent

      # pull out the HTTP headers
      my $ct = mapscript::msIO_stripStdoutBufferContentType();

      # and then pick up the actual content of the response
      my $content = mapscript::msIO_getStdoutBufferString();

      my $xml  = XML::LibXML->new();
      my $xslt = XML::LibXSLT->new();

      # load XML content
      my $source = $xml->parse_string($content);

      # load XSLT document
      my $style_doc = $xml->parse_file($xsltfile);
      my $stylesheet = $xslt->parse_stylesheet($style_doc);

      # invoke the XSLT transformation
      my $results = $stylesheet->transform($source);
      # print out the result (header + content)
      print "Content-type: $ct\n\n";
      print $stylesheet->output_string($results);
    }

    # else process as normal
    else {
      $dispatch = $map->OWSDispatch($req);
    }


=============================================================================
 Java Example
=============================================================================

One benefit of redirection of output to a buffer is that it is thread-safe.
Several threads in the same process can be actively processing requests and
writing their results to distinct output buffers.  This Java example, used
to test multi-threaded access demonstrates that.

.. code-block:: java

  import edu.umn.gis.mapscript.mapObj;
  import edu.umn.gis.mapscript.OWSRequest;
  import edu.umn.gis.mapscript.mapscript;

  class WxSTest_thread extends Thread {

    public String	mapName;
    public byte[]       resultBytes;

    public void run() {
	mapObj map = new mapObj(mapName);

        map.setMetaData( "ows_onlineresource", "http://dummy.org/" );

        OWSRequest req = new OWSRequest();

        req.setParameter( "SERVICE", "WMS" );
        req.setParameter( "VERSION", "1.1.0" );
        req.setParameter( "REQUEST", "GetCapabilities" );

        mapscript.msIO_installStdoutToBuffer();

        int owsResult = map.OWSDispatch( req );

        if( owsResult != 0 )
            System.out.println( "OWSDispatch Result (expect 0): " + owsResult );

        resultBytes = mapscript.msIO_getStdoutBufferBytes();
    }
  }

  public class WxSTest {
    public static void main(String[] args)  {
        try {
            WxSTest_thread tt[] = new WxSTest_thread[100];
            int i;
            int expectedLength=0, success = 0, failure=0;

            for( i = 0; i < tt.length; i++ )
            {
                tt[i] = new WxSTest_thread();
                tt[i].mapName = args[0];
            }
            
            for( i = 0; i < tt.length; i++ )
                tt[i].start();

            for( i = 0; i < tt.length; i++ )
            {
                tt[i].join();
                if( i == 0 )
                {
                    expectedLength = tt[i].resultBytes.length;
                    System.out.println( "Document Length: " + expectedLength + ", expecting somewhere around 10000 or more." );
                }
                else if( expectedLength != tt[i].resultBytes.length )
                {
                    System.out.println( "Document Length:" + tt[i].resultBytes.length + " Expected:" + expectedLength );
                    failure++;
                }
                else
                    success++;
            }

            System.out.println( "Successes: " + success );
            System.out.println( "Failures: " + failure );

        } catch( Exception e ) {
            e.printStackTrace();
        }
    }
  }

=============================================================================
 PHP Example
=============================================================================

Most of the same capabilities are accessible in php mapscript. Here
is an example displaying a :ref:`wms capabilities <wms_capabilities>`.



.. code-block:: php

  Example1 : get the capabilities
  This is for example what a url could look like :
  http://.../php/ows.php?service=WMS&version=1.1.1&Request=GetCapabilities  


  <?php

  dl("php_mapscript_4.10.0.dll");

  $request = ms_newowsrequestobj();

  $request->loadparams();

  /*exampple on how to modify the parameters :
   forcing the version from 1.1.1 to 1.1.0 */
  $request->setParameter("VeRsIoN","1.1.0");

  ms_ioinstallstdouttobuffer();

  $oMap = ms_newMapobj("../../service/wms.map");

  $oMap->owsdispatch($request);

  $contenttype = ms_iostripstdoutbuffercontenttype();

  $buffer = ms_iogetstdoutbufferstring();

  header('Content-type: application/xml');
  echo $buffer;

  ms_ioresethandlers();

  ?>


  Example2 : get the map
  This is for example what a url could look like :
   
  http://.../php/ows.php?SERVICE=WMS&VeRsIoN=1.1.1&Request=GetMap&
  LAYERS=WorldGen_Outline  


  <?php

  dl("php_mapscript_4.10.0.dll");

  $request = ms_newowsrequestobj();

  $request->loadparams();
 
  ms_ioinstallstdouttobuffer();

  $oMap = ms_newMapobj("../../service/wms.map");

  $oMap->owsdispatch($request);

  $contenttype = ms_iostripstdoutbuffercontenttype();

  if ($contenttype == 'image/png')
     header('Content-type: image/png');

  ms_iogetStdoutBufferBytes();

  ms_ioresethandlers();

  ?> 

=============================================================================
 Use in Non-CGI Environments (mod_php, etc)
=============================================================================

The loadParams() call establish parses the cgi environment varabiables
(QUERY_STRING, and REQUEST_METHOD) into parameters in the OWSRequest object.  
In non-cgi environments, such as when php, python and perl are used as 
"loaded modules" in Apache, or Java with Tomcat, the loadParams() call 
will not work - in fact in 4.10.x it will terminate the web server instance. 

It is necessary in these circumstances for the calling script/application 
to parse the request url into keyword/value pairs and assign to the 
OWSRequest object by other means, as shown in some of the above examples 
explicitly setting the request parameters. 
 
=============================================================================
 Post Processing Capabilities
=============================================================================

In the following python example, we process any incoming WxS request, but if it is
a GetCapabilities request we replace the Service section in the capabilities
with a section read from a file, that is carefully tailored the way we want.

.. code-block:: python

  #!/usr/bin/env python

  import sys
  import elementtree.ElementTree as ET

  import mapscript

  req = mapscript.OWSRequest()
  req.loadParams()

  map = mapscript.mapObj( '/u/www/maps/ukpoly/ukpoly.map' )

  #
  # Handle anything but a GetCapabilities call normally. 
  #
  if req.getValueByName('REQUEST') <> 'GetCapabilities':

    map.OWSDispatch( req )

  #
  # Do special processing for GetCapabilities 
  #
  else:
    mapscript.msIO_installStdoutToBuffer()

    map.OWSDispatch( req )

    ct = mapscript.msIO_stripStdoutBufferContentType()
    content = mapscript.msIO_getStdoutBufferString()
    mapscript.msIO_resetHandlers()

    # Parse the capabilities.
    
    tree = ET.fromstring(content)

    # Strip out ordinary Service section, and replace from custom file.
    
    tree.remove(tree.find('Service'))
    tree.insert(0,ET.parse('./Service.xml').getroot())

    # Stream out adjusted capabilities.
    
    print 'Content-type: ' + ct
    print
    print ET.tostring(tree)
