**FREE
//  This is a JSON-based web service consumer for the web
//  service provided by the CUST001R example.
//                              Scott Klement, Sept 11, 2018
//
//  - Uses a green-screen display
//  - Uses DATA-INTO to read JSON
//  - Uses DATA-GEN to create JSON
//  - Uses HTTPAPI to consume the service
//
//  Before Compiling:
//   - install HTTPAPI (current version preferred)
//   - install YAJL
//   - make sure both HTTPAPI and YAJL are found in your *LIBL
//   - Update BASEURL constant with the correct URL for
//       your environment.
//
//  To Compile:
//  *> CRTDSPF FILE(CUST002D) SRCFILE(QDDSSRC)
//  *> CRTBNDRPG CUST002R SRCFILE(QRPGLESRC) DBGVIEW(*LIST)
//

ctl-opt dftactgrp(*no) actgrp(*new)
        bnddir('HTTPAPI')
        option(*srcstmt:*nodebugio:*noshowcpy);

dcl-f CUST002D workstn indds(dspf) sfile(SFL:RRN);

dcl-ds ORIG likerec(MAINT:*ALL);

/INCLUDE HTTPAPI_H

dcl-ds cust qualified;                  // {
   success  ind             inz(*on);   //   "success": true|false,
   errorMsg varchar(500)    inz('');    //   "errorMsg": "{string}",
   num_data int(10)         inz(0);
   dcl-ds data dim(999);                //   "data": [ {
      custno packed(5: 0)   inz(0);     //     "custno": {number},
      name   varchar(30)    inz('');    //     "name": "{string}",
      dcl-ds address;                   //     "address": {
         street varchar(30) inz('');    //       "street": "{string}",
         city   varchar(20) inz('');    //       "city": "{string}",
         state  char(2)     inz('  ');  //       "state": "{string}",
         postal varchar(10) inz('');    //       "postal": "{string}"
      end-ds;                           //     }
   end-ds;                              //   } ]
end-ds;                                 // }

dcl-ds dspf qualified;
   F3     ind pos(3);
   F10    ind pos(10);
   F12    ind pos(12);
   sflclr ind pos(50);
   sfldsp ind pos(51);
end-ds;

dcl-s rrn packed(4: 0);
dcl-s recsLoaded like(rrn);
dcl-c BASEURL 'http://localhost:8500/api/customers';
dcl-c JOB_CCSID 0;


*inlr = *on;

http_debug(*on);
http_setCCSIDs(1208: JOB_CCSID);

dow '1';

   if loadSflList() = *off;
      return;
   endif;

   if showList() = *off;
      return;
   endif;

enddo;


// -------------------------------------------------------------------
//   clearSFL():  Clears all records from the subfile that shows
//                the list of available customers
// -------------------------------------------------------------------
dcl-proc clearSfl;

   dspf.sflclr = *on;
   dspf.sfldsp = *off;
   write CTL;
   dspf.sflclr = *off;
   rrn = 0;
   recsLoaded = 0;

end-proc;


// -------------------------------------------------------------------
//   loadSflList(): Loads the subfile with the list of available
//                  customers.
//
//     - Uses HTTPAPI to retrieve the list in JSON format
//     - If HTTP server asks for a password, handles that.
//     - Uses YAJL to parse the JSON
//     - Loads result into subfile.
//
//   Returns *ON if all is well, *OFF if an error was found
// -------------------------------------------------------------------
dcl-proc loadSflList;

   dcl-pi *n ind;
   end-pi;

   dcl-s jsonData varchar(100000);
   dcl-s i int(10);
   dcl-s errMsg varchar(500);

   dcl-s err int(10);


   // Retrieve the list of customers (get userid/password if needed)

   dou err <> HTTP_NDAUTH;

      monitor;
         jsonData = http_string( 'GET' : BASEURL);
         msg = *blanks;
         err = 0;
      on-error;
         msg = http_error(err);
      endmon;

      // site needs a sign-on

      if err = HTTP_NDAUTH;
         if GetPassword() = *off;
            return *off;
         endif;
      endif;

   enddo;

   if err <> 0;
      msg = http_error();
      return *off;
   endif;

   data-into cust %DATA( jsonData
                       : 'case=convert countprefix=num_')
                  %PARSER('YAJLINTO');

   clearSfl();

   if cust.success = *off;
      errMsg = cust.errorMsg;
   endif;

   for i = 1 to cust.num_data;

      custno = cust.data(i).custno;
      name   = cust.data(i).name;
      street = cust.data(i).address.street;
      city   = cust.data(i).address.city;
      state  = cust.data(i).address.state;
      postal = cust.data(i).address.postal;
      opt    = *blanks;

      RRN = i;
      recsLoaded = RRN;
      write SFL;

      dspf.sfldsp = *on;
   endfor;

   return *on;

end-proc;


// -------------------------------------------------------------------
//   showList(): Display the subfile containing customer list
//
//     - Displays the (already loaded) subfile
//     - If F10 pressed, calls the newCust() routine
//     - Loops through subfile, if any record selected, calls
//         modifyCust() for that record.
//
//   Returns *ON if the list should be re-loaded
//          *OFF if the user asked for F3=Exit
// -------------------------------------------------------------------
dcl-proc showList;

   dcl-pi *n ind;
   end-pi;

   dou dspf.F3;

      write ftr;
      exfmt ctl;
      msg = *blanks;

      if dspf.F3;
         iter;
      endif;

      if dspf.F10 = *on;
         newCust();
         leave;
      endif;

      for rrn = 1 to recsLoaded;

         chain rrn SFL;
         if %found and opt <> ' ';
            if modifyCust(CUSTNO) = *off;
               leave;
            endif;
            opt = ' ';
            update SFL;
         endif;

       endfor;

    enddo;

   return not dspf.F3;
end-proc;


// -------------------------------------------------------------------
//   loadCust(): Retrieve customer information for a single customer
//               from the web service provider.
//
//   custno = (input) customer number to retrieve
//
//      - uses HTTPAPI to ask provier for customer information
//      - uses YAJL to parse JSON information
//      - loads JSON information into screen fields
//
//  Returnss *ON if successful, *OFF if an error occured
// -------------------------------------------------------------------
dcl-proc loadCust;

   dcl-pi *n ind;
      custno like(ORIG.custno) value;
   end-pi;

   dcl-s jsonData varchar(100000);
   dcl-s err int(10);
   dcl-s errMsg varchar(500);

   name   = *blanks;
   street = *blanks;
   city   = *blanks;
   state  = *blanks;
   postal = *blanks;

   monitor;
      jsonData = http_string( 'GET': BASEURL + '/' + %char(custno));
      msg = *blanks;
      err = 0;
   on-error;
      msg = http_error(err);
   endmon;

   data-into cust %DATA( jsonData
                       : 'case=convert countprefix=num_')
                  %PARSER('YAJLINTO');

   if cust.success = *off;
      errMsg = cust.errorMsg;
   endif;

   if cust.num_data > 0;
      custno = cust.data(1).custno;
      name   = cust.data(1).name;
      street = cust.data(1).address.street;
      city   = cust.data(1).address.city;
      state  = cust.data(1).address.state;
      postal = cust.data(1).address.postal;
   endif;

   orig.name   = name;
   orig.street = street;
   orig.city   = city;
   orig.state  = state;
   orig.postal = postal;

   return *on;

end-proc;

dcl-proc modifyCust;

   dcl-pi *n ind;
      custno like(ORIG.custno) value;
   end-pi;

   if loadCust(custno) = *off;
      return *off;
   endif;

   exfmt MAINT;
   msg = *blanks;

   if dspf.F12 or dspf.F3;
      dspf.F12 = *off;
      return *off;
   endif;

   return updateCust(custno: *off);

end-proc;


// -------------------------------------------------------------------
//  newCust(): Create a new customer record
//
//   - Clears customer fields to start with a blank customer
//   - Displays screen, lets user enter customer information
//   - Calls the 'updateCust' routine to send results to provider
//
//  Returns *ON if successful, *OFF if error or user cancelled
// -------------------------------------------------------------------
dcl-proc newCust;

   dcl-pi *n ind;
   end-pi;

   clear orig;
   name   = *blanks;
   street = *blanks;
   city   = *blanks;
   state  = *blanks;
   postal = *blanks;
   custno = 0;

   exfmt MAINT;
   msg = *blanks;

   if dspf.F12 or dspf.F3;
      dspf.f12 = *off;
      return *off;
   endif;

   return updateCust(custno: *on);

end-proc;


// -------------------------------------------------------------------
//  updateCust(): Call the web service provider to update cust info
//
//     custno = (input) customer number to update
//      isNew = (input) pass *ON to add a new customer, *off otherwise
//
//    - Builds a JSON document containing the customer fields
//         (only those that have been changed)
//    - Uses HTTPAPI to send JSON to provider
//
//  Returns *ON if successful, *OFF upon failure
// -------------------------------------------------------------------
dcl-proc updateCust;

   dcl-pi *n ind;
      custno like(ORIG.custno) value;
      isNew ind const;
   end-pi;

   dcl-s jsonData varchar(10000);
   dcl-s url varchar(1000);
   dcl-s method varchar(10);

   dcl-ds cust qualified;                   // {
      success  ind             inz(*on);    //   "success": true,
      errorMsg varchar(500)    inz('');     //   "errorMsg": "{string}",
      dcl-ds data;                          //   "data": {
         num_custno int(10)      inz(0);
         custno     packed(5: 0) inz(0);    //     "custno": {number},
         num_name   int(10)      inz(0);
         name       varchar(30)  inz('');   //     "name": {string},
         dcl-ds address;                    //     "address": {
            num_street int(10)   inz(0);
            street  varchar(30)  inz('');   //       "street": "{string}",
            num_city   int(10)   inz(0);
            city    varchar(20)  inz('');   //       "city": "{string}",
            num_state  int(10)   inz(0);
            state   char(2)      inz('  '); //       "state": "{string}",
            num_postal int(10)   inz(0);
            postal  varchar(10)  inz('');   //       "postal": "{string}"
         end-ds;                            //     }
      end-ds;                               //   }
   end-ds;                                  // }


   if orig.name <> name;
      cust.data.num_name = 1;
      cust.data.name = %trim(name);
   endif;

   if orig.street <> street;
      cust.data.address.num_street = 1;
      cust.data.address.street     = %trim(street);
   endif;
   if orig.City <> city;
      cust.data.address.num_city = 1;
      cust.data.address.city     = %trim(city);
   endif;
   if orig.State <> state;
      cust.data.address.num_state = 1;
      cust.data.address.state     = %trim(state);
   endif;
   if orig.Postal <> postal;
      cust.data.address.num_postal = 1;
      cust.data.address.postal     = %trim(postal);
   endif;

   data-gen cust %data(jsonData: 'countprefix=num_')
                 %gen('YAJLDTAGEN');

   if isNew;
      method = 'POST';
      url = BASEURL;
   else;
      method = 'PUT';
      url = BASEURL + '/' + %char(custno);
   endif;

   monitor;
      http_string( method: url: jsonData
                 : 'application/json; charset=utf-8' );
   on-error;
      msg = http_error();
      return *off;
   endmon;

   return *on;
 end-proc;


// -------------------------------------------------------------------
//   GetPassword(): Ask the user for a userid/password to log in
//                  to the web service provider
//
//    - Uses HTTP_getAuth() routine to discover the type of
//        password the provider allows
//    - Asks user for their credentials
//    - Uses HTTP_setAuth() to supply the credentials to HTTPAPI
//
//  Returns *ON if successful, *OFF if user chose F3=Exit
// -------------------------------------------------------------------
 dcl-proc GetPassword;

   dcl-pi *n ind;
   end-pi;

   dcl-s Basic ind;
   dcl-s Digest ind;
   dcl-s NTLM ind;
   dcl-s HttpRealm char(124);
   dcl-s Type char(1);

   http_getAuth( Basic: Digest: HttpRealm: NTLM );
   Realm = HttpRealm;

   exfmt SignIn;

   if dspf.F3;
      return *off;
   endif;

   // Select from the authentication types that this service allows.
   //   NTLM is the most secure so use it if available.
   //   if not, fall back to Digest.
   //   finally use Basic (unencrypted) if nothing else is available.

   select;
   when NTLM;
      type = HTTP_AUTH_NTLM;
   when Digest;
      type = HTTP_AUTH_MD5_DIGEST;
   other;
      type = HTTP_AUTH_BASIC;
   endsl;

   http_setAuth( type: UserId: Password );
   return *on;

end-proc; 