Pages

Thursday, October 17, 2013

How to create a separate transaction using UserConnection to ensure your transaction is not rolled back at a higher level

Sometimes, you may have a need to have some code commit the SQL call it is making, regardless of what is happening with the code that called it, and the easiest way to do that is by creating a separate user transaction.

The reason I needed to do this was I have a static method MyCode::SendEmail(...) that would send an email and drop a log-record into a table to let me know that the email was sent.  So if it was called by some code in a transaction, and then for whatever reason, that transaction had to be rolled back, I still wanted a record of that email being sent...because it was actually sent.

So to do this, you just create a new UserConnection(), do userConnection.ttsBegin(), and then assign your table to that connection via table.setConnection(userConnection), and then a userConnection.ttsCommit() at the end.  See my attached sample job.



static void HowToCreateSeperateTransaction(Args _args)
{
    Table1          table1;
    boolean         doAbort = true;
    UserConnection  userConnection = new UserConnection();
    ;

    delete_from table1;
    
    ttsbegin;
    
    // This row "1" will only appear when the trans is not aborted
    table1.Field1 = '1';
    table1.insert();

    // This row "2" should always appear no matter what
    userConnection.ttsbegin();
    table1.clear();
    table1.setConnection(userConnection);
    table1.Field1 = '2';
    table1.insert();
    userConnection.ttscommit();

    if (doAbort)
        ttsabort;
    else
        ttscommit;
    
    new SysTableBrowser().run(tableNum(Table1));
}

Tuesday, October 15, 2013

A simple, pseudo-explanation of how AIF works, intended for a developer who wants the gist of the functionality

This is a StackOverflow response that I gave, but I thought it might be good for a blog post as well.  It explains the "gist" or central idea of how the AIF works from a programmers view, touching on how AifDocumentLog, AifQueueManager, AifGatewayQueue, AifMessageLog, etc get populated and used.

There are 4 main AIF classes, I will be talking about the inbound only, and focusing on the included file system adapter and flat XML files.  I hope this makes things a little less hazy.


  1. AIFGatewayReceiveService - Uses adapters/channels to read messages in from different sources, and dumps them in the AifGatewayQueue table
  2. AIFInboundProcessingService - This processes the AifGatewayQueue table data and sends to the Ax<Document> classes
  3. AIFOutboundProcessingService - This is the inverse of #2. It creates XMLs with relevent metadata
  4. AIFGatewaySendService - This is the inverse of #1, where it uses adapters/channels to send messages out to different locations from the AifGatewayQueue



AIFGatewayReceiveService (#1):

So this basically fills the AifGatewayQueue, which is just a queue of work.  It loops through all of your channels and then finds the relevant adapter by ClassId.  The adapters are classes that implement AifIntegrationAdapter and AifReceiveAdapter, if you wanted to make your own custom one.  When it loops over the different channels, it then loops over each inbound "message" waiting to be picked up and tries to receive it into the queue.

If it can't process the file for some reason, it catches exceptions and throws them in the SysExceptionTable [Basic>Periodic>Application Integration Framework>Exceptions].  These messages are scraped from the infolog, and the messages are generated mostly from the receive adapter, which would be AifFileSystemReceiveAdapter for my example.



AIFInboundProcessingService (#2):

So this is processing the inbound messages sitting in the queue (ready/inprocess).  The Classes\AifRequestProcessor\processServiceRequest does the work.

From this method, it will call:

  • Various calls to Classes\AifMessageManager, which puts records in the AifMessageLog and the AifDocumentLog.
  • This key line: responseMessage = AifRequestProcessor::executeServiceOperation(message, endpointActionPolicy); which actually does the operation against the Ax<Document> classes by eventually getting to AifDispatcher::callServiceMethod(...)
  • It gets the return XML and packages that into an AifMessage called responseMessage and returns that where it may be logged.  It also takes that return value, and if there is a response channel, it submits that back into the AifGatewayQueue


Wednesday, October 9, 2013

How to find what projects an object from the AOT exists in

Where I work, we are required to create a new, numbered project for every modification request.  The numbered projects have corresponding documentation for the intended purpose.

So when I later want to find out what projects an AOT object exists in, I needed a way to search inside every project.  I wrote this proof of concept job on how to find in what projects a specific object exists.  Enjoy!


static void FindWhatProjectsObjectExistsIn(Args _args)
{
    ProjectNode         pn;
    ProjectListNode     projectListNode;

    TreeNode            tn, tn2;
    TreeNodeIterator    tni, tni2;

    // Object we are searching for
    TreeNode            tnSearch = TreeNode::findNode(@'\Forms\SalesTable');
    ;

    projectListNode = SysTreeNode::getSharedProject();
    tni = projectListNode.AOTiterator();

    tn = tni.next();

    while (tn)
    {
        pn = tn; // ProjectNode inherits TreeNode
        pn = pn.loadForInspection();

        tni2 = pn.AOTiterator();

        tn2 = tni2.next();

        while (tn2)
        {
            if (tn2.treeNodePath() == tnSearch.treeNodePath())
                info(strfmt("Found in shared project %1", tn.AOTname()));

            // info(tn2.applObjectType()); // Returns the type (Form/Class/Table/Etc)
            // info(tn2.AOTname()); // Returns the object name
            // info(tn2.treeNodePath()); // Returns the object path
            
            tn2 = tni2.next();
        }

        tn = tni.next();
    }
}