**FREE
//  This is a JSON-based web service consumer for the web
//  service provided by the CUST001R example. This is the
//  "original" version that does not require DATA-INTO.
//                              Scott Klement, March 6, 2017
//
//  - Uses a green-screen display
//  - Uses YAJL to process JSON
//  - Uses HTTPAPI to consume the service
//
//  Before Compiling:
//   - install HTTPAPI version 1.33 or newer
//   - 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' : 'YAJL')
        option(*srcstmt:*nodebugio:*noshowcpy);

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

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

/INCLUDE HTTPAPI_H
/INCLUDE YAJL_H

dcl-ds dspf qualified;
   F3     ind pos(3);
   F10    ind pos(10);
   F12    ind pos(12);
   view   ind pos(40);
   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/webservices/json/cust';
dcl-c BASEURL 'http://localhost:8080/profoundui/auth/universal/skcust/json/cust';
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 field varchar(50);
   dcl-s jsonData varchar(100000);
   dcl-s i int(10);
   dcl-s j int(10);
   dcl-s k int(10);
   dcl-s errMsg varchar(500);

   dcl-s docNode   like(yajl_val);
   dcl-s node      like(yajl_val);
   dcl-s arrayNode like(yajl_val);
   dcl-s custNode  like(yajl_val);
   dcl-s dataNode  like(yajl_val);
   dcl-s addrNode  like(yajl_val);

   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;

   // Parse JSON and load it into subfile

   docNode = yajl_buf_load_tree( %addr(jsonData:*data)
                               : %len(jsonData)
                               : ErrMsg );
   if errMsg <> '';
      msg = errMsg;
      return *off;
   endif;

   clearSfl();

   node = yajl_object_find( docNode: 'success' );
   if yajl_is_false(node);
      node = yajl_object_find( docNode: 'errorMsg');
      errMsg = yajl_get_string(node);
   endif;

   arrayNode = yajl_object_find( docNode: 'data' );

   i = 0;
   dow yajl_array_loop(arrayNode: i: dataNode);

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

      j = 0;
      dow yajl_object_loop(dataNode: j: field: custNode);

         select;
         when field = 'name';
            exsr load_field;
         when field = 'custno';
            exsr load_field;
         when field = 'address';

            k = 0;
            dow yajl_object_loop(custNode: k: field: addrNode);
               exsr load_field;
            enddo;

         endsl;

      enddo;

      RRN = i;
      recsLoaded = RRN;
      write SFL;

      dspf.sfldsp = *on;

   enddo;

   yajl_tree_free(docNode);

   return *on;


   begsr load_field;

      select;
      when field = 'custno';
         custno = yajl_get_number(custNode);
      when field = 'name';
         name = yajl_get_string(custNode);
      when field = 'street';
         street = yajl_get_string(addrNode);
      when field = 'city';
         city = yajl_get_string(addrNode);
      when field = 'state';
         state = yajl_get_string(addrNode);
      when field = 'postal';
         postal = yajl_get_string(addrNode);
      endsl;

   endsr;

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 field varchar(50);
   dcl-s jsonData varchar(100000);
   dcl-s i int(10);
   dcl-s j int(10);

   dcl-s docNode   like(yajl_val);
   dcl-s node      like(yajl_val);
   dcl-s custNode  like(yajl_val);
   dcl-s dataNode  like(yajl_val);
   dcl-s addrNode  like(yajl_val);

   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;

   // Parse JSON and load it onto display

   docNode = yajl_buf_load_tree( %addr(jsonData:*data)
                               : %len(jsonData)
                               : ErrMsg );
   if errMsg <> '';
      msg = errMsg;
      return *off;
   endif;

   node = yajl_object_find( docNode: 'success' );
   if yajl_is_false(node);
      node = yajl_object_find( docNode: 'errorMsg');
      errMsg = yajl_get_string(node);
   endif;

   dataNode = yajl_object_find( docNode: 'data' );

   i = 0;
   dow yajl_object_loop(dataNode: i: field: custNode);

      select;
      when field = 'name';

         exsr load_field;

      when field = 'address';

         j = 0;
         dow yajl_object_loop(custNode: j: field: addrNode);
            exsr load_field;
         enddo;

      endsl;

   enddo;

   yajl_tree_free(docNode);

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

   return *on;

   begsr load_field;

      select;
      when field = 'name';
        name = yajl_get_string(custNode);
      when field = 'street';
        street = yajl_get_string(addrNode);
      when field = 'city';
        city = yajl_get_string(addrNode);
      when field = 'state';
        state = yajl_get_string(addrNode);
      when field = 'postal';
        postal = yajl_get_string(addrNode);
      endsl;

   endsr;

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 newLen int(10);
   dcl-s url varchar(1000);
   dcl-s method varchar(10);

   yajl_genOpen(*on);
   yajl_beginObj();

   yajl_addBool('success': *on);
   yajl_beginObj('data');

   if orig.name <> name;
      yajl_addChar('name': %trim(name));
   endif;

   yajl_beginObj('address');

   if orig.street <> street;
      yajl_addChar('street': %trim(street));
   endif;
   if orig.City <> city;
      yajl_addChar('city': %trim(city));
   endif;
   if orig.State <> state;
      yajl_addChar('state': %trim(state));
   endif;
   if orig.Postal <> postal;
      yajl_addChar('postal': %trim(postal));
   endif;

   yajl_endObj();
   yajl_endObj();
   yajl_endObj();

   %len(jsonData) = %len(jsonData:*MAX);
   yajl_copyBuf( JOB_CCSID
               : %addr(jsonData:*data)
               : %len(jsonData)
               : newLen );

   %len(jsonData) = newLen;

   yajl_genClose();

   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; 
