
<erl>


box(Str) ->
    {'div',[{class,"box"}],
     {pre,[], yaws_api:htmlize(Str)}}.

tbox(T) ->
    box(lists:flatten(io_lib:format("~p",[T]))).


ssi(File) ->
    {'div',[{class,"box"}],
     {pre,[],
      {ssi, File,[],[]}}}.

ss(A, File) ->
    {ok, B} = file:read_file(
                filename:join([A#arg.docroot, File])),
    box(binary_to_list(B)).



out(A) ->
    [{ssi, "TAB.inc", "%%",[{"soap_intro", "choosen"}]},
     {ehtml,
      {'div', [{id, "entry"}],

      [{h1, [], "SOAP with Yaws"},

       {p, [],
        ["SOAP is an XML-based protocol for communication over a network "
         "connection. The main focus of SOAP is remote procedure calls (RPCs) "
         "transported via HTTP. "
         "SOAP is similar to XML-RPC but makes use of XML Schema to define "
         "the data types it uses. "
         ]},

       {h2, [], "Preparations"},

       {p, [],
        ["Yaws uses the 'erlsom' XML Schema "
         "parser and some SOAP specific library code. "
         "Thus, to be able to use SOAP with Yaws you need to have "
         "'erlsom' installed. Currently, the easiest way of installing "
         "'erlsom' is to check out the library from github.com and "
         "install it from there (you can also download a released version "
         "of erlsom and install it)."]},

       {p,[],"To install 'erlsom' do:"},

       box("git clone https://github.com/willemdj/erlsom.git\n"
           "cd erlsom; chmod a+x configure; ./configure; make\n"
           "sudo make install        # iff you want to install as root\n"
          ),

          {p, [],
           "<b>Important:</b> The SOAP-specific code that makes use of erlsom has some limitations "
           "that it is important to be aware of. Only the Soap <i>'document'</i> binding "
           "style is supported. There is no support for non-soap bindings, nor for the "
           "RPC binding style. Also, only the <i>'literal'</i> encoding is supported "
           "There is no support for <i>'soap-encoding'</i>. For an explanation of the differences "
           "between these concepts, see this "
           "<a href=\"https://www-128.ibm.com/developerworks/webservices/library/ws-whichwsdl/\">description</a>."},


          {h2, [], "The SOAP client side"},

       {p, [],
        ["The SOAP interface is defined by a WSDL specification, which "
         "simply is a (rather verbose) piece of XML document that specifies "
         "the public interface for a web service. As a client, "
         "we don't need to understand everything in the WSDL specification "
         "The parts that are most interesting is the name of the operation "
         "we want to perform (i.e the function we want to call) and what "
         "input data it expects."
        ]},

       {p,[],
        ["As an example, lets have a look at a public SOAP service that "
         "returns some weather data for the location we send to it. "
         "The WSDL specification can be found here: ",
         {a, [{href, "https://www.webservicex.net/WeatherForecast.asmx?WSDL"}],
          "https://www.webservicex.net/WeatherForecast.asmx?WSDL "}
         ]},

       {p,[],
        ["We start by finding the operation named: ",
         {i, [], "GetWeatherByPlaceName, "},
         "which is the operation we want to invoke. As can be seen, we have "
         "one input message and one output message defined. The input message is "
         "the one we (as a client) will send to the server. "
        ]},

       box("<wsdl:operation name=\"GetWeatherByPlaceName\">\n"
           "  <documentation>\n"
           "    Get one week  weather forecast for a place name(USA)\n"
           "  </documentation>\n"
           "  <wsdl:input message=\"tns:GetWeatherByPlaceNameSoapIn\"/>\n"
           "  <wsdl:output message=\"tns:GetWeatherByPlaceNameSoapOut\"/>\n"
           "</wsdl:operation>\n"),

       {p,[],
        ["Now, follow the reference to the message: ",
         {i, [], "tns:GetWeatherByPlaceNameSoapIn, "},
         "to where it is defined: "
        ]},

       box("<wsdl:message name=\"GetWeatherByPlaceNameSoapIn\">\n"
           "<wsdl:part name=\"parameters\" element=\"tns:GetWeatherByPlaceName\"/>\n"
           "</wsdl:message>\n"),

       {p,[],
        ["Continue by following the reference to: ",
         {i, [], "tns:GetWeatherByPlaceName, "},
         "and you will end up with an XML Schema type definition: "
        ]},

       box("<s:element name=\"GetWeatherByPlaceName\">\n"
           "<s:complexType>\n"
           "<s:sequence>\n"
           "<s:element minOccurs=\"0\" maxOccurs=\"1\" name=\"PlaceName\" type=\"s:string\"/>\n"
           "</s:complexType>\n"
           "</s:sequence>\n"
           "</s:element>\n"),

       {p,[],
        "This tells us that the function we want to call takes one argument "
        "of a string type (which apparently denotes a Name of a place in the US). "
        "Left for us is just to call the function from an Erlang shell which has "
        "got the Yaws ebin directory in the path:"
       },

       box("1> inets:start().\n"
           "ok\n"
           "2> yaws_soap_lib:call(\n"
           "      \"https://www.webservicex.net/WeatherForecast.asmx?WSDL\",\n"
           "      \"GetWeatherByPlaceName\",\n"
           "      [\"Boston\"]).\n"
           "{ok,undefined,\n"
           "  [{'p:GetWeatherByPlaceNameResponse',\n"
           "     [],\n"
           "     {'p:WeatherForecasts',[],\n"
           "       \"40.3044128\",\n"
           "       \"79.81284\",\n"
           "       \"0.000453\",\n"
           "       \"42\",\n"
           "       \"BOSTON\",\n"
           "       \"PA\",\n"
           "       undefined,\n"
           "       {'p:ArrayOfWeatherData',\n"
           "         [],\n"
           "         [{'p:WeatherData',\n"
           "         [],\n"
           "         \"Friday, December 08, 2006\"|...},\n"
           "         .....\n"),

       {p,[],
        "So what happened here? We specified the URL to the WSDL file. "
        "The yaws_soap_lib:call/3 function then went to retrieve the file, parsed it, "
        "created a proper message, sent off the message, waited for the "
        "reply and finally returned a parsed reply as Erlang records. "
       },

       {p,[],
        "Even though this is very convenient, we probably want do more than just one call "
        "to the web service. So to avoid retrieving and parsing the WSDL file for every "
        "call. We can do it in two steps: "
        },

       box("1> inets:start().\n"
           "ok\n"
           "2> Wsdl = yaws_soap_lib:initModel(\n"
           "      \"https://www.webservicex.net/WeatherForecast.asmx?WSDL\").\n"
           "...\n"
           "3> yaws_soap_lib:call(\n"
           "      Wsdl,\n"
           "      \"GetWeatherByPlaceName\"\n"
           "      [\"Boston\"]).\n"
          ),

       {p,[],
        "To be able to work with the records that we get in the response, "
        "we can create a header file that we can include in our source code. In our example "
        "the generated '.hrl' file will look like this: "
        },

       box("4> yaws_soap_lib:write_hrl(Wsdl, \"/tmp/wfc.hrl\").\n"
           "...\n"
           "5> {ok,Bin}=file:read_file(\"/tmp/wfc.hrl\"),io:fwrite(binary_to_list(Bin)).\n"
           "-record('soap:detail', {anyAttribs, choice}).\n"
           "-record('soap:Fault', {anyAttribs, 'faultcode', 'faultstring', 'faultactor', 'detail'}).\n"
           "-record('soap:Body', {anyAttribs, choice}).\n"
           "-record('soap:Header', {anyAttribs, choice}).\n"
           "-record('soap:Envelope', {anyAttribs, 'Header', 'Body', choice}).\n"
           "-record('p:GetWeatherByPlaceNameResponse', {anyAttribs, 'GetWeatherByPlaceNameResult'}).\n"
           "-record('p:GetWeatherByPlaceName', {anyAttribs, 'PlaceName'}).\n"
           "-record('p:WeatherData', {anyAttribs, 'Day', 'WeatherImage', 'MaxTemperatureF', \n"
           "                          'MinTemperatureF', 'MaxTemperatureC', 'MinTemperatureC'}).\n"
           "-record('p:ArrayOfWeatherData', {anyAttribs, 'WeatherData'}).\n"
           "-record('p:WeatherForecasts', {anyAttribs, 'Latitude', 'Longitude', 'AllocationFactor', \n"
           "                               'FipsCode', 'PlaceName', 'StateCode', 'Status', 'Details'}).\n"
           "-record('p:GetWeatherByZipCodeResponse', {anyAttribs, 'GetWeatherByZipCodeResult'}).\n"
           "-record('p:GetWeatherByZipCode', {anyAttribs, 'ZipCode'}).\n"
          ),

       {p,[],
        "As you can see, every record in our header has an XML namespace prefix prepended "
        "in the name of the record. The prefix 'p' as shown above is the default prefix you'll "
        "get if you don't specify a prefix yourself. This is probably good enough, but if you "
        "want to set it to something else, you can do it as shown below:"
        },

       box("6> yaws_soap_lib:initModel(... , \"foo\").           % foo is my prefix\n"
           "7> yaws_soap_lib:write_hrl(... , ... , \"foo\").\n"
           ),

       {p,[],
        ["Some final notes:",
         {ul, [],
          [{li, [],
            "The \"https://...\" URL given as the first argument to the "
            "functions above may as well be a local file, and thus written as \"file://....\". "},
           {li, [],
            "When we retrieve a HTTP located file, we will use 'ibrowse' if it exist "
            "in the code path. Otherwise we will use the OTP 'http' client."},
           {li, [],
            "The prefix ('foo' in the example above) is passed to erlsom - it is one of erlsom's "
            "options. If you want to specify other options, you can also pass the regular erlsom "
	    "options to yaws_soap_lib:initModel/2 and yaws_soap_lib:write_hrl/3. For example, to "
	    "specify how files that the XSD inside the WSDL refers to via 'include' statements "
	    "can be retrieved, you can pass it a function GetIncludes/4 by specifying "
	    "[{include_fun, GetIncludes}]. See the erlsom documentation for other options that you "
            "could specify."}
          ]}]},

       {h2, [], "The SOAP server side"},

       {p,[],
        "If we want to run our own weather service we need to take the WSDL "
        "and add our own location to it. Either we can just study the WSDL file to "
        "see which URL we need to change in the 'service' part of the document, or "
        "we can make use of some nice access functions that work on the "
        "#wsdl{} record that yaws_soap_lib:initModel/2 returned, as shown below: "
        },

       box("8> Ops = yaws_soap_lib:wsdl_operations(Wsdl).\n"
           "9> {ok,Op} = yaws_soap_lib:get_operation(Ops, \"GetWeatherByPlaceName\").\n"
           "10> yaws_soap_lib:wsdl_op_address(Op).\n"
           "\"https://www.webservicex.net/WeatherForecast.asmx\"\n"
          ),

       {p,[],
        "Now, edit the WSDL file and change the above URL to something like this:"
        },

       box("<wsdl:service name=\"WeatherForecast\">\n"
           "  <documentation xmlns=......\n"
            "  <wsdl:port name=\"WeatherForecastSoap\".....\n"
           "    <soap:address location=\"https://localhost:8181/WeatherForecast.yaws\" />\n"
           "  </wsdl:port>\n"
           ".....\n"
          ),


       {p,[],
        "Next, start an Erlang shell and start Yaws with SOAP enabled. We need to write "
        "the code that returns the weather info. This is done in a callback module that "
        "the Yaws SOAP code will call with the incoming message. The message will be an "
        "Erlang record and what we return must also be an Erlang record. So we will need "
        "to create a .hrl containing the record definitions that we can include: "
        },

       box("1> Docroot = \"/tmp\".\n"
           "\n"
           "2> GL = [{enable_soap,true},   % <== THIS WILL ENABLE SOAP IN A YAWS SERVER!!\n"
           "         {trace, false},\n"
           "         {tmpdir,Docroot},{logdir,Docroot},\n"
           "       {flags,[{tty_trace, false},{copy_errlog, true}]}].\n"
           "\n"
           "3> SL = [{port,8181},{servername,\"localhost\"},{dir_listings, true},\n"
           "         {listen,{127,0,0,1}},{flags,[{auth_log,false},{access_log,false}]}].\n"
           "\n"
           "% BELOW, WE CREATE THE .hrl FILE!!\n"
           "4> yaws_soap_lib:write_hrl(\"file:///tmp/MyWeatherService.wsdl\", \"/tmp/my_soap.hrl\").\n"
           "\n"
           "% WE MUST ADD A PATH TO OUR CALLBACK CODE!!\n"
           "5> code:add_path(Docroot).\n"
           ),

       {p,[],
        "We continue by writing our weather forecast callback module:"
        },

       box("# cat /tmp/my_soap.erl\n"
           "-module(my_soap).\n"
           "-export([handler/4]).\n"
           "-include(\"my_soap.hrl\").  % .hrl file generated by erlsom\n"
           "\n"
           "handler(_Header,\n"
           "        [#'p:GetWeatherByPlaceName'{'PlaceName' = Place}],\n"
           "        _Action, \n"
           "        _SessionValue) ->\n"
           "    {ok, undefined, get_weather_info(Place)}.\n"
           "\n"
           "get_weather_info(Place) ->\n"
           "    WeatherData =\n"
           "    #'p:WeatherData'{anyAttribs = [],\n"
           "             'Day' = \"Sunday, December 10, 2006\",\n"
           "             'WeatherImage' = \"https://www.nws.noaa.gov/weather/images/fcicons/nfew.jpg\",\n"
           "             'MaxTemperatureF' = \"51\",\n"
           "             'MinTemperatureF' = \"28\",\n"
           "             'MaxTemperatureC' = \"11\",\n"
           "             'MinTemperatureC' = \"-2\"\n"
           "            },\n"
           "\n"
           "    ArrayOfWeatherData =\n"
           "    #'p:ArrayOfWeatherData'{anyAttribs = [],\n"
           "                'WeatherData' = [WeatherData]\n"
           "                   },\n"
           "\n"
           "    Forecast =\n"
           "      #'p:WeatherForecasts'{anyAttribs = [],\n"
           "                'Latitude' = \"40.3044128\",\n"
           "                'Longitude' = \"79.81284\",\n"
           "                'AllocationFactor' = \"0.000453\",\n"
           "                'FipsCode' = \"42\",\n"
           "                'PlaceName' = Place,\n"
           "                'StateCode' = \"PA\",\n"
           "                'Status' = undefined,\n"
           "                'Details' = ArrayOfWeatherData\n"
           "               },\n"
           "\n"
           "    Response =\n"
           "    #'p:GetWeatherByPlaceNameResponse'{anyAttribs = [],\n"
           "                       'GetWeatherByPlaceNameResult' = Forecast\n"
           "                      },\n"
           "\n"
           "    [Response]. \n"
          ),

       {p,[],
        "The final piece on the server side is the '.yaws' file that invokes the "
        "Yaws SOAP server (note that we are using the same way of hooking in our "
        "callback module as for Json and HaXe):"
        },

       box("# cat /tmp/WeatherForecast.yaws\n"
           "<erl>\n"
           "out(A) ->\n"
           "    yaws_rpc:handler_session(A, {my_soap, handler}).\n"
           "</erl>\n"
          ),

       {p,[],
        "Now, in your Yaws shell, setup the Soap server as shown below. (If required, for "
	"example to specify a prefix or a function to retrieve included files, you can specify "
	"options similar to what we saw above for yaws_soap_lib:initModel/2 and "
	"yaws_soap_lib:write_hrl/3 , using yaws_soap_srv:setup/3.)"
        },

       box("6> yaws:start_embedded(Docroot,SL,GL).\n"
           "=INFO REPORT==== 29-Nov-2008::20:03:50 ===\n"
           "Yaws: Listening to 127.0.0.1:8181 for servers\n"
           " - http://localhost:8181 under /tmp\n"
           "ok\n"
           "7> yaws_soap_srv:setup({my_soap, handler}, \"file:///tmp/MyWeatherService.wsdl\").\n"
           "ok\n"
          ),

       {p,[],
        "We are now ready to try it out. Start another Erlang shell and call it: "
        },

       box("1> inets:start().\n"
           "ok\n"
           "2> yaws_soap_lib:call(\"file:///tmp/MyWeatherService.wsdl\",\n"
           "                      \"GetWeatherByPlaceName\",\n"
           "                      [\"Stockholm\"]).\n"
           "{ok,undefined,\n"
           "  [{'p:GetWeatherByPlaceNameResponse', [],\n"
           "     {'p:WeatherForecasts',[],\n"
           "       \"40.3044128\",\n"
           "       \"79.81284\",\n"
           "       \"0.000453\",\n"
           "       \"42\",\n"
           "       \"Stockholm\",           % <=== Yippie, it works !!\n"
           "       \"PA\",\n"
           "       undefined,\n"
           "       {'p:ArrayOfWeatherData', [],\n"
           "         [{'p:WeatherData', [],\n"
           "           \"Sunday, December 10, 2006\"|...}]}}}]}\n"
           "\n"
          ),

       {p,[],
        "There you have it! "
        },


     {ssi, "END2",[],[]}
    ]}}].




</erl>

