lunes, 15 de agosto de 2011

mainOnServer madness

It had to happen. So let me explain.

We've seen the mainOnServer trick used in a few classes now and it was time to copy/paste the concept. The idea is simple enough and the comment in the PurchFormLetter class is crystal clear:
Main method is split to two parts to reduce the interaction between tiers. main method runs on the client. It gets parameters from Args class and passes them to mainOnServer method, which does the main job.
In my case it was importing 10 to 20,000 ledger entries from a text file. Not a simple import either as we had to translate values from another host system and perform numerous validations. It all made sense to read the text file into a TextBuffer and send it across to the server tier.

The first problem occurred when attempting to export the list of generated errors to Excel. I would be using a TextBuffer to create a tab separated file with a list of table headers and then iterate over buffer fields of the problematic rows. The X++ method performPushAndFormatting of the class SysGridExportToExcel wasn't working as expected, mainly due to a problem with the clipboard. So... Here are two ways to clear the clipboard, neither of which was working - why was that?
    TextBuffer   tbErr   = new TextBuffer();
    ;
    tbErr.setText('');
    // Clear clipboard 1/2    
    tbErr.toClipboard();
    // Clear clipboard 2/2
    WinApi::emptyclipboard();
I tried and tried and couldn't clear the clipboard on my machine for some reason. Finally I completely gave up on the SysGridExportToExcel::performPushAndFormatting(...) idea and went for the approach of generating a csv file and then letting the OS decide which application should open it. I adapted the idea to use a TextBuffer as well, writing the contents to a file on the machine... Which also didn't work.
    FileIoPermission _perm;
    str tmpFileName;
;
    tmpFileName = strfmt('%1ax_export\\%2',WinAPI::getTempPath(),"test.csv");
    _perm = new FileIoPermission(tmpFileName,'RW');
    _perm.assert();
    tbErr.toFile(tmpFileName);

    //Open CSV file in registered app 
    WinAPI::shellExecute(tmpFileName);
The code executes with no problems whatsoever until the last command which could never find the file we had generated. I ended up using Excel && Com (example) to create the output but this could take a very long time but there was still some niggling doubt in my mind as to what the source of the problem really was.


Agghhh! We were executing on the server all along! Simply by creating a static client method it would have resolved my issues.

I'm just going to copy and paste my code below, hightlighting the bits you'd need to change were you to use it (at your own risk!). It's not generic enough to simply pass a query or buffer into a method, but adapt it as you see fit:
static client void exportErrorsToExcel(EVE_HRImportHistory importHistory)
{
    QueryRun                qr;

    Common                  ljTrans;        // EVE_DOMImportLedgerJournalTrans
    int                     cnt     = 0;

    DictTable               dt      = new DictTable(tablenum(EVE_DOMImportLedgerJournalTrans));
    FieldId                 fieldId;
    SysDictField            fieldDict;
    int                     fieldNumDim = fieldNum(EVE_DOMImportLedgerJournalTrans, Dimension);

    TextBuffer              tbErr   = new TextBuffer();
    str                     sTab    = '\t';
    str                     sNL     = '\r\n';
    int                     iTab    = strlen(sTab);

    // Update Excel Output columns - 33
    container               moneyCols               = [18,19];
    // 0 = string, 1 = ??, 2 = money, 3 = date, 4 = ??
    container               formatCols              = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,0,2,2,0,0,0,0,0,0,0,0,0,0,0,0];
    // 1 == left  2 == right  3 == center
    container               alignmentCols           = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2,2,1,1,1,1,1,1,1,1,1,1,1,1,1];
    ;

    // Add title rows
    fieldId = dt.fieldNext(0);
    while (fieldId)
    {
        if (!isSysId(fieldId))
        {
            // Heading Initialisations
            fieldDict = new SysDictField(dt.id(), fieldId);
            tbErr.appendText(fieldDict.label());
            tbErr.appendText(sTab);
        }
        fieldId = dt.fieldNext(fieldId);
    }
    tbErr.delete(tbErr.size()-iTab+1,iTab);      // Avoid last column
    tbErr.appendText(sNL);
    cnt++;

    // Query
    qr = EVE_DOMDirectDebitImporter::getImportHistoryBatch(importHistory.ID, true);
    while(qr.next())
    {
        ljTrans = qr.getNo(1);

        fieldId = dt.fieldNext(0);
        while (fieldId)
        {
            if (!isSysId(fieldId))
            {
                if (fieldId != fieldNumDim)   // Dimensions!  Exclude from report :)
                {
                    tbErr.appendText(strfmt("%1",ljTrans.(fieldId)));
                } else {
                    tbErr.appendText("EXCLUIDO");
                }
                tbErr.appendText(sTab);
            }
            fieldId = dt.fieldNext(fieldId);
        }
        tbErr.delete(tbErr.size()-iTab+1,iTab);
        tbErr.appendText(sNL);
        cnt++;
    }

    tbErr.toClipboard();
    SysGridExportToExcel::performPushAndFormatting(moneyCols, alignmentCols, formatCols, cnt);

    tbErr = null;
}

1 comentario: