#!/usr/bin/perl

#
# XML to Jython converter. Written by Vivin S. Paliath on June 24th, 2009
#
# Modified on December 5th, 2009. Query parameters in a GET request weren't being properly parsed and included in the parameters hash. Fixed this.
#


 use strict;
 use XML::Simple;
 use Data::Dumper;

 my $version = "0.1";
 my $file = $ARGV[0];
 my $outputfile = $ARGV[1];
 my $filehandle;

 my $INIT_TAB = " " x 0;
 my $CLASS_TAB = " " x 6;
 my $DEF_TAB = " " x 4;
 my $IF_TAB = " " x 3;
 my $ELSE_TAB = " " x 3;

 my @months = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec);
 my @weekDays = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
 my ($second, $minute, $hour, $dayOfMonth, $month, $yearOffset, $dayOfWeek, $dayOfYear, $daylightSavings) = localtime();
 my $year = 1900 + $yearOffset;
 my $theTime = "$hour:$minute:$second, $weekDays[$dayOfWeek] $months[$month] $dayOfMonth, $year";

 my %headers;

 # Used in the initializeTask method to make sure that all urls used in this task have been set
 my $urlDictCheckString = "if(";
 my $urlList = "[";

 my $xml = XML::Simple->new(KeyAttr=>[], ForceArray=>1); # Setting these attributes to maintain consistency and prevent folding
 my $data = $xml->XMLin($file);

 my $i = 0;

 if($outputfile eq "") {
    $filehandle = *STDOUT;
 }

 else {
    open(OUT, ">$outputfile");
    $filehandle = *OUT;
 }

 my $className = "RecordedTask";
 my $firstComment = $data->{"page"}->[0]->{"request"}->[0]->{"comment"}->[0];
 $firstComment =~ s/"/\\"/g;
 $firstComment =~ s/\s+/ /g;
 $firstComment =~ s/\n//g;

 my($pageMethodName, $commentString) = split(/\s*:\s*/, $firstComment);
 $pageMethodName =~ s/\s//g;

 if($firstComment =~ /^@/) {
    $firstComment =~ s/ .*$//;
    $firstComment =~ s/@//;
    $className = $firstComment;
 }

 print $filehandle "# Converted from XML to Jython by xmlToJython.pl on $theTime\n";
 print $filehandle "# " . $data->{"metadata"}->[0]->{"version"}->[0] . "\n";
 print $filehandle "# HTTP script recorded by TCPProxy at " . $data->{"metadata"}->[0]->{"time"}->[0] . "\n\n";

 print $filehandle "from infusion.test.framework.Task import Task\n";
 print $filehandle "from HTTPClient import NVPair\n";
 print $filehandle "from net.grinder.plugin.http import HTTPPluginControl, HTTPRequest\n";
 print $filehandle "from net.grinder.script import Test\n";
 print $filehandle "from net.grinder.script.Grinder import grinder\n\n";

 if($className eq "RecordedTask") {
    print $filehandle "# You probably want to change the name of this task later\n";
    print $filehandle "# I suggest doing a global search and replace of \"RecordedTask\" with a name of your choosing\n";
}

 print $filehandle "class $className(Task):\n\n";

 print $filehandle "$INIT_TAB$CLASS_TAB" . "connectionDefaults = HTTPPluginControl.getConnectionDefaults()\n";
 print $filehandle "$INIT_TAB$CLASS_TAB" . "httpUtilities = HTTPPluginControl.getHTTPUtilities()\n\n";

 print $filehandle "$INIT_TAB$CLASS_TAB# To use a proxy server, uncomment the next line and set the host and port.\n";
 print $filehandle "$INIT_TAB$CLASS_TAB# connectionDefaults.setProxyServer(\"localhost\", 8001)\n\n";

 print $filehandle "$INIT_TAB$CLASS_TAB# These definitions at the top of the class are Class Variables (as opposed to\n";
 print $filehandle "$INIT_TAB$CLASS_TAB# Instance Variables) and are shared between all instances of this class.\n\n";

 foreach my $header(@{$data->{"common-headers"}}) {
    $headers{$header->{"headers-id"}} = $header->{"header"}   
 }

 $i = 0;
 my $numUris = scalar(@{$data->{"base-uri"}});
 foreach my $base_uri(@{$data->{"base-uri"}}) {
      if($i == 0) {
         $urlDictCheckString .= "not self.urlDict.has_key(\"" . $base_uri->{"uri-id"} . "\") ";
      }
      else {
         $urlDictCheckString .= "or not self.urlDict.has_key(\"" . $base_uri->{"uri-id"} . "\") ";
      }
      $urlList .= $base_uri->{"uri-id"} . ", ";
      $i++;
 }

 $urlDictCheckString =~ s/ $//;
 $urlDictCheckString .= "):";
 $urlList =~ s/, $//;
 $urlList .= "]";

 foreach my $headerId(sort{
                         if($a eq 'defaultHeaders') { return -1; }
                         elsif($b eq 'defaultHeaders') { return 1; }
                         else { my $tmp_a = $a; my $tmp_b = $b; $tmp_a =~ s/[^0-9]//g; $tmp_b =~ s/[^0-9]//g; return $tmp_a <=> $tmp_b; }
                      }(keys(%headers))){
    if($headerId eq "defaultHeaders") {
       print $filehandle "$INIT_TAB$CLASS_TAB" . "connectionDefaults.defaultHeaders = \\\n";
    }

    else {
       print $filehandle "$INIT_TAB$CLASS_TAB$headerId = \\\n";
    }

    print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB(\n";

    foreach my $headerAttributes(@{$headers{$headerId}}) {
       print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB  NVPair('" . $headerAttributes->{"name"} . "', '" . $headerAttributes->{"value"} . "'),\n" 
    }

    print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB)\n\n";
 }

 print $filehandle <<"JYTHON";
${INIT_TAB}${CLASS_TAB}def __init__(self):
${INIT_TAB}${CLASS_TAB}${DEF_TAB}"""Initialize properties of class"""
${INIT_TAB}${CLASS_TAB}${DEF_TAB}Task.__init__(self)
${INIT_TAB}${CLASS_TAB}${DEF_TAB}self.description = "$commentString"
${INIT_TAB}${CLASS_TAB}${DEF_TAB}self.urlDict = {}
${INIT_TAB}${CLASS_TAB}${DEF_TAB}self.taskId = Task.numberOfTasks

${INIT_TAB}${CLASS_TAB}def initializeTask(self):
${INIT_TAB}${CLASS_TAB}${DEF_TAB}"""Initializes Instance Variables for this class. This method will be called by the Scenario object that this task belongs to."""
${INIT_TAB}${CLASS_TAB}${DEF_TAB}$urlDictCheckString
${INIT_TAB}${CLASS_TAB}${DEF_TAB}${IF_TAB}raise Exception(self.__class__.__name__ + ".urlDict is missing values for one or more of the following keys: $urlList. Please define them in the constructor for the parent Scenario.")
${INIT_TAB}${CLASS_TAB}${DEF_TAB}else:
JYTHON

 my $pageNumber = 1;
 my $comment = "";
 my %parameters;
 my %methods;
 my %tokens;
 my $instrumentMethodsString = "";

 foreach my $page(@{$data->{'page'}}) {
    my $requestNumber = 1;
    my $requestRangeString = "";
    my $totalRequests = scalar @{$page->{'request'}};
    my $lastRequest = 1 + ($totalRequests - 1);
    my $methodBodyStr = "";
    my $description;
    my $firstSleepFound = 0; # First sleep for a page appears in the run() method. Subsequent sleeps appear within the method for the page itself

    $lastRequest = (length($lastRequest) == 1 ? "0$lastRequest" : $lastRequest);

    if($totalRequests == 1) {
       $requestRangeString = "(request ${pageNumber}01)";
    }

    else {
       $requestRangeString = "(requests ${pageNumber}01-$pageNumber$lastRequest)";
    }

    foreach my $request(@{$page->{'request'}}) {
       my $requestSuffix = "$pageNumber" . (length($requestNumber) == 1 ? "0$requestNumber" : $requestNumber);
       my $extendsUri = $request->{"uri"}->[0]->{"extends"};
       my $extendsHeader = $request->{"headers"}->[0]->{"extends"};
       
       $description = $request->{"description"}->[0];
       $description =~ s/\n//g;

       $comment = (length($request->{"comment"}->[0]) > 0) ? $request->{"comment"}->[0] : $comment;
       $comment =~ s/@[^ ]+ //;
       $comment =~ s/"/\\"/g;
       $comment =~ s/\s+/ /g;
       $comment =~ s/\n//g;

       my($pageMethodName, $commentString) = split(/\s*:\s*/, $comment);
       $pageMethodName = ($pageMethodName eq "") ? "page" : $pageMethodName;
       $pageMethodName =~ s/\s//g;
       $commentString = ($commentString eq "") ? "Page $pageNumber" : "$commentString: ";
       $commentString =~ s/"/\\"/g;

       # If we have a token in the response, we need to store it in the %tokens hash 
       if($request->{"response"}->[0]->{"token-reference"}) {
          $tokens{$request->{"response"}->[0]->{"token-reference"}->[0]->{"token-id"}} = $request->{"response"}->[0]->{"token-reference"}->[0]->{"new-value"}->[0];
       }

       # If we have a body it means that we're POST'ing data; we need to grab those fields and values and put them into a hash
       if($request->{"body"}) {
          foreach my $NVPair(@{$request->{"body"}->[0]->{"form"}->[0]->{"form-field"}}) {
             $parameters{"$pageMethodName$pageNumber"}->{"$requestSuffix"}->{$NVPair->{"name"}} = $NVPair->{"value"};
          }
       }

       elsif($request->{"uri"}->[0]->{"query-string"}) { 
          # Looks like we've got a query string and we're using GET
          # Some nodes don't have a token-reference. They just have the a query string with no arguments. So we want 
          # to make sure that we do have arguments
          my $tokenReference = $request->{"uri"}->[0]->{"query-string"}->[0]->{"token-reference"};
          
          if($tokenReference) {
             foreach my $NVPair(@{$tokenReference}) {
                # In the XML, when a token doesn't have a value, the tag looks like <new-value />
                # When this happens, $NVPair->{"new-value"}->[0] is an empty hash. 
                my $token_value = ($NVPair->{"new-value"} ? (ref $NVPair->{"new-value"}->[0] eq "HASH" ? "" : $NVPair->{"new-value"}->[0]) : $tokens{$NVPair->{"token-id"}});
                $token_value =~ s/\s//g;

                my $token_id = $NVPair->{"token-id"};
                $token_id =~ s/token_//;
                $parameters{"$pageMethodName$pageNumber"}->{"$requestSuffix"}->{$token_id} = $token_value;
             }
          }
       }

       # Generating code to set request objects as Instance Variables
       print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB" . "self.request$requestSuffix = HTTPRequest(url=self.urlDict[\"$extendsUri\"], headers=$className.$extendsHeader)\n";
       print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB" . "self.request$requestSuffix = Test(int(str(self.taskId) + str($requestSuffix)), self.__class__.__name__ + \"(\" + str(self.taskId) + \") $commentString$description\").wrap(self.request$requestSuffix)\n\n";

       # While we're iterating through the pages and requests, we'll build our methods too. Here we're figuring out how to handle POSTs and GETs. POSTs expect a name-value pair
       # with NVPair, whereas with a GET you can simply construct a querystring

       if($request->{"method"}->[0] eq "POST") {
          my $path;

          # Some requests seem to have query strings along with POSTs, which really sucks in my opinion. I'm not sure why you would be sending stuff in a querystring if you are using POST
          # in the first place. Right now I'm just tacking it along to the path. Eventually it would be nice to have it parameterized.

          if($request->{"uri"}->[0]->{"query-string"}) {
             $path = $request->{"uri"}->[0]->{"unparsed"}->[0]
          }
 
          else {
             $path = $request->{"uri"}->[0]->{"path"}->[0]->{"text"}->[0];
          }

          $path =~ s/\s//g;

          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB" . ($requestNumber == 1 ? "result = " : "") . "self.request$requestSuffix.POST('$path',\n";
          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB$DEF_TAB(\n";

          foreach my $key(keys(%{$parameters{"$pageMethodName$pageNumber"}->{$requestSuffix}})) {
             $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB$DEF_TAB  NVPair('$key', self.parameters[\"$pageMethodName$pageNumber\"][\"$requestSuffix\"][\"$key\"]),\n"
          }

          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB$DEF_TAB),\n";
          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB$DEF_TAB( NVPair('" . $request->{"headers"}->[0]->{"header"}->[0]->{"name"} . "', '" . $request->{"headers"}->[0]->{"header"}->[0]->{"value"} . "'), )\n";
          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB)\n";
       }

       elsif($request->{"method"}->[0] eq "GET") {
          my $path = $request->{"uri"}->[0]->{"path"}->[0]->{"text"}->[0];
          $path =~ s/\s//g;

          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB" . ($requestNumber == 1 ? "result = " : "") . "self.request$requestSuffix.GET('$path'";

          # We only want to add a query string if we have parameters in this method
          if(exists $parameters{"$pageMethodName$pageNumber"}) {
             # We also want to make sure that the current request has parameters. You might wonder why we don't do if(exists $parameters{"$pageMethodName$pageNumber"}->{$requestSuffix}
             # This is because perl autovivifies (creates if something doesn't exist) any intervening references. So we'll end up with references to empty hashes, which we don't want
             if(exists $parameters{"$pageMethodName$pageNumber"}->{$requestSuffix}) {
                $methodBodyStr .= "+\n";
                my $i = 0;
                foreach my $key(keys(%{$parameters{"$pageMethodName$pageNumber"}->{$requestSuffix}})) {
                   my $queryStringStart = ($i == 0 ? "?" : "");
                   $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB$DEF_TAB'$queryStringStart$key=' + self.parameters[\"$pageMethodName$pageNumber\"][\"$requestSuffix\"][\"$key\"] + '&'\n";
                }
                $methodBodyStr =~ s/ \+ '&'$//;
                $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB)\n";
             }

             else {
                # This method has parameters defined, but for another request
                $methodBodyStr .= ")\n";
             }
          }

          elsif(exists $request->{"uri"}->[0]->{"query-string"}->[0]->{"text"}) {
             # This means we have a strange query string, in which case we're just going to add the unparsed value
             $methodBodyStr .= " + '?" . $request->{"uri"}->[0]->{"query-string"}->[0]->{"text"}->[0] . "')\n";
          }

          else {
             $methodBodyStr .= ")\n";
          }
       }
 
       if($request->{"response"}->[0]->{"status-code"} ne "200") {
          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB# Expecting " . $request->{"response"}->[0]->{"status-code"} . "'" . $request->{"response"}->[0]->{"reason-phrase"} . "'\n";
          if($request->{"response"}->[0]->{"token-reference"}) {
             my $token_id = $request->{"response"}->[0]->{"token-reference"}->[0]->{"token-id"};
             my $id = $token_id;
             $id =~ s/token_//;

             my $token_value = $request->{"response"}->[0]->{"token-reference"}->[0]->{"new-value"}->[0];
             $token_value =~ s/\s//g;
             $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB" . "self.$token_id = $className.httpUtilities.valueFromLocationURI('$id') # $token_value\n\n";
          }
       }

       else {
          $methodBodyStr .= "\n";
       }
 
       if($firstSleepFound and $request->{"sleep-time"}) {
          $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB" . "grinder.sleep(" . $request->{"sleep-time"}->[0] . ")\n\n";
       }

       else {
          $methods{"$pageMethodName$pageNumber"}->{"first-sleep"} = $request->{"sleep-time"}->[0];
          $firstSleepFound = 1;
       }
       $requestNumber++;
    }

    $methodBodyStr .= "$INIT_TAB$CLASS_TAB$DEF_TAB" . "return result";

    my($pageMethodName, $commentString) = split(/\s*:\s*/, $comment);

    $instrumentMethodsString .= "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB" . "self.instrumentMethod(Test(int(str(self.taskId) + str(${pageNumber}00)), self.__class__.__name__ + \"(\" + str(self.taskId) + \") $commentString\"), '$pageMethodName$pageNumber')\n";

    $methods{"$pageMethodName$pageNumber"}->{"signature"} = "$INIT_TAB$CLASS_TAB" . "def $pageMethodName$pageNumber(self):";
    $methods{"$pageMethodName$pageNumber"}->{"commenstring"} = "$INIT_TAB$CLASS_TAB$DEF_TAB# $commentString $description $requestRangeString.";
    $commentString =~ s/"/\\"/g;
    $methods{"$pageMethodName$pageNumber"}->{"docstring"} = "$INIT_TAB$CLASS_TAB$DEF_TAB\"\"\"$commentString $description $requestRangeString.\"\"\"";
    $methods{"$pageMethodName$pageNumber"}->{"body"} = $methodBodyStr;

    $pageNumber++;
 }

 print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB" . "self.parameters = \\\n";
 print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB" . "{\n";
 
 $i = 0;
 my $numPages = scalar(keys(%parameters));

 foreach my $page(sort{
                     my $tmp_a = $a; my $tmp_b = $b; $tmp_a =~ s/[^0-9]//g; $tmp_b =~ s/[^0-9]//g; return $tmp_a <=> $tmp_b;
                  }(keys(%parameters))){

   print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB\"$page\":\n";
   print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB" . "{\n";

   my $numRequests = scalar(keys(%{$parameters{$page}}));
   my $j = 0;

   foreach my $request(sort{$a <=> $b}(keys(%{$parameters{$page}}))) {
      print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB$DEF_TAB\"$request\":\n";
      print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB$DEF_TAB" . "{\n";

      my $numNVPairs = scalar(keys(%{$parameters{$page}->{$request}}));
      my $k = 0;

      foreach my $key(keys(%{$parameters{$page}->{$request}})) {
         print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB$DEF_TAB$DEF_TAB\"$key\":\"" . $parameters{$page}->{$request}->{$key} . "\"" . (($k == $numNVPairs - 1) ? "\n" : ",\n");
         $k++;
      }

      print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB$DEF_TAB}" . (($j == $numRequests - 1) ? "\n" : ",\n");
      $j++;
   }
   
   print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB$DEF_TAB}" . (($i == $numPages - 1) ? "\n" : ",\n");
   $i++;
 }

 print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB$ELSE_TAB}\n\n";

 print $filehandle "$instrumentMethodsString\n";

 foreach my $methodName(sort{
                           my $tmp_a = $a; my $tmp_b = $b; $tmp_a =~ s/[^0-9]//g; $tmp_b =~ s/[^0-9]//g; return $tmp_a <=> $tmp_b;
                        }(keys(%methods))){
    print $filehandle $methods{$methodName}->{"signature"} . "\n";
    print $filehandle $methods{$methodName}->{"docstring"} . "\n";
    print $filehandle $methods{$methodName}->{"body"} . "\n\n";
 }

 print $filehandle "$INIT_TAB$CLASS_TAB" . "def run(self):\n";
 print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB\"\"\"The run() method runs all the tests in this Task\"\"\"\n";

 foreach my $methodName(sort{
                           my $tmp_a = $a; my $tmp_b = $b; $tmp_a =~ s/[^0-9]//g; $tmp_b =~ s/[^0-9]//g; return $tmp_a <=> $tmp_b;
                        }(keys(%methods))){
    print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB" . "self.callParameterizingMethodFor('$methodName')\n";
    print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB" . "self.$methodName()   " . $methods{$methodName}->{"commentstring"} . "\n";

    if($methods{$methodName}->{"first-sleep"}) {
       print $filehandle "$INIT_TAB$CLASS_TAB$DEF_TAB" . "grinder.sleep(" . $methods{$methodName}->{"first-sleep"} . ")\n";
    }

    print $filehandle "\n";
 }

# my $d = Data::Dumper->new([\%methods], ["methods"]);
# $d->Sortkeys(1);
# print $d->Dump;
