sábado, 31 de março de 2012

Trabalhando com arquivos no eScript

O Siebel possui sua própria linguagem script para programação: o eScript.
De acordo com as boas práticas só se programa em eScript quando não existem outras opções para aplicar a lógica de negócios no sistema (por conta de facilidade de realizar atualizações de versão no Siebel). Isso inclui, não raramente, a necessidade de ler ou escrever arquivos no sistema de arquivos aonde o servidor Siebel está instalado (ou no sistema de arquivos do Siebel Client, mas não vou falar de Browser Scripting neste artigo).
Para manipulação de arquivos o eScript oferece a biblioteca Clib: esta biblioteca oferece funções muito parecidas com aquelas existentes na linguagem ANSI C. Não vou repetir aqui o que já se encontra documentado no respectivo Siebel Bookshelf (Siebel eScript Language Reference) mas sim citar alguns macetes aprendidos depois de quebrando a cabeça por aí.

Teste o diretório antes de usá-lo


Antes de usar um diretório é sempre uma boa idéia verificar se o script tem acesso a ele (principalmente se a versão do Siebel que você está trabalhando não tem a função Clib.strerror trabalhando corretamente). Uma forma simples de testar isto é utilizar as funções Clib.chdir e Clib.getcwd em combinação, como mostrado na função testDir definida abaixo:

function testDir(dir)
{
    try
    {
        //tests if the directory is valid
        var previousDir = Clib.getcwd();
        var testResult = -1;
       
        testResult = Clib.chdir( dir );
       
        if ( testResult == -1 )
        {
            TheApplication().RaiseErrorText('The directory ' + dir + ' does not exists or cannot be accessed.');
        }
    }
    catch(e)
    {
        TheApplication().RaiseErrorText('Error in function testDir: ' + e.toString());
    }
}


Isso vai ajudar você a identificar erros de permissão (ou não existência) do diretório antes de tentar usá-lo.

Teste se o arquivo foi aberto

Parece besteira, mas às vezes acho alguns programas por aí que simplesmente não fazem um teste depois que abrem um arquivo com Clib.fopen. É muito simples fazer isto, como mostrado abaixo:

    fp = Clib.fopen(myFile,"au");

    if ( fp == null )
    {
        TheApplication().RaiseErrorText('An error ocurrered when trying to open ' + myFile);
    }


Com três linhas de código você evitará ter que fazer debugging por conta de erro nas permissões no sistema de arquivo, por exemplo. Por que isso? Porque esses erros não são considerados falhas graves: a Clib irá falhar silenciosamente...
A propósito, as funções Clib.fputs e Clib.fclose seguem o mesmo raciocínio, retornando null no caso de terem problemas. Se você estiver se sentido particularmente paranoico também pode incluir os mesmos tipos de testes.

Favoreça o uso do buffer

As funções de leitura e escrita da Clib utilizam buffer por padrão: isso quer dizer que elas guardam em memória os dados antes de realizar uma operação de E/S. Isso favorece o desempenho do seu programa porque operações de E/S em disco são mais lentas do que em memória (não estou nem considerando o cache do disco). Se você fizer isto dentro do mesmo escopo:

    fp = Clib.fopen(myFile,"au");

    if ( fp == null )
    {
        TheApplication().RaiseErrorText('An error ocurrered when trying to open ' + myFile);
    }
   
    Clib.fputs(myData,fp);
    Clib.fclose(fp);


você simplesmente desperdiça o buffer. Uma vez argumentaram comigo que utilizar Clib para mensagens de registro era reinventar a roda já que para este fim existe a função TheApplication().Trace. O problema desta função é que você não tem controle sobre o buffer: ela escreve imediatamente isto no disco, ou seja, abre o arquivo, escreve e imediatamente fecha o arquivo depois.
Já a Clib permite que você tenha controle sobre como o buffer se comporta e se você estiver escrevendo grandes quantidades de dados o buffer poderá te ajudar.
Para isto, basta criar uma variável (global, se você for usá-la durante o Business Service todo) e abrir o arquivo apenas uma vez com Clib.fopen. Após isto, basta seguir utilizando Clib.fputs e seus primos.
Como garantir então que o arquivo será fechado? Isso é particularmente um problema se o servidor Siebel está utilizando um sistema de arquivos com locking mandatório como o NTFS: seu arquivo ficará "preso" até que o processo que o executou explicitamente feche ele ou deixe de existir.
Para evitar este tipo de problema basta usar o bloco finally para fechar o arquivo.

# na área de declarations
    var fp;
   
function foobar {

    try {

    fp = Clib.fopen(myFile,"au");

    if ( fp == null )
    {
        TheApplication().RaiseErrorText('An error ocurrered when trying to open ' + myFile);
    }
   
    Clib.fputs(myData,fp);
   
    } catch(e) {
   
        //do something
   
    } finally {
   
        Clib.fclose(fp);
   
    }
   
}


Com isto, a menos que o processo que está executando o Business Service seja encerrado (e o locking arquivo liberado no processo) garantidamente o arquivo será fechado e quaisquer dados que ainda não tenha sido escritos o serão antes do arquivo ser fechado. Com isto você tem maiores garantidas de que os dados não foram perdidos (não totalmente pelo menos) e o locking do arquivo seja removido pelo sistema de arquivos.
Precisa que os dados sejam escritos (por maior garantia de não perder os dados) no arquivo a cada Clib.fputs? Use a Clib.fflush para este fim, sem fechar o arquivo.

Copiando arquivos

O artigo "How Can You Copy Files from One Location to Another Using Siebel Scripting?" (ID 477948.1) disponível na página de suporte da Oracle ensina algumas alternativas de como resolver isto já que a Clib, por algum motivo que eu não entendo muito bem, não tem funções específicas para esta funcionalidade tão comum.
Das alternativas que ele ensina, a melhor mesmo é utilizar Clib.rename: se você já usou alguma vez o prompt de algum SO UNIX ele se parece muito com o comando mv.
As outras alternativas (Clib.system e SElib) são "custosas" demais para fazer isto e mais complicadas de utilizar. Se for necessário copiar muitos arquivos, talvez seja interessante então criar um programa pequeno em outra linguagem de programação e então executá-lo com Clib.system, assim você executa este programa uma única vez e ele faz todo o trabalho de uma vez.

Listando arquivos em um diretório

Essa dica só é válida para instalações do Siebel em SO's da Microsoft (Windows).
O suporte da Oracle tem artigos ensinando a usar SElib e a DLL kernel32.dll para conseguir procurar por padrões de arquivos em algum diretório qualquer. Pena que o artigo só ajuda até certo ponto: já vi código por aí chamando a função FindFirstFileA do kernel32.dll recursivamente quando a função FindNextFileA está lá disponível justamente para resolver estas questões. É um processo doloroso ler a documentação (principalmente porque ela é para programadores C++, o que não é o meu caso) mas ela esta lá (http://msdn.microsoft.com/en-us/library/windows/desktop/aa364418%28v=vs.85%29.aspx) justamente para evitar este tipo de bobagem.
Outro problema é lidar com caracteres nulos: um tamanho pré-definido de memória é alocada para guardar o nome do arquivo que for encontrado: na falta de algo melhor, as funções FindFirstFileA e FindNextFileA vão preencher o que restar do tamanho alocado com caracteres nulos caso o nome do arquivo seja menor. Parece normal se você estiver programando em C ou C++, mas tente concatenar strings (ou qualquer outra coisa, como usar split) com eScript e com o resultado devolvido e você verá que as coisas não vão funcionar como esperado.
A função abaixo é uma solução possível para resolver este tipo de problema: ela irá procurar o primeiro caracter nulo na string e retornar o número de caracteres "imprimíveis" lidos até então.

function removeNul(string)
{
    try
    {
        var iEndPos = string.length;
        var lastPrintChar = 0;

        for (var i = 0; i < iEndPos; i++) {
 
            //if char is printable, go to next until test fails
            if ( Clib.isprint( string.charAt(i) ) )
            {
           
                lastPrintChar++;
           
            }
            else {
           
                break;
           
            }
       
        }
       
        return string.substring(0, lastPrintChar);
   
    }
    catch(e) {
   
        logMessage('An error ocurred with removeNul function: ' + e.errText);
   
    }

}


Abaixo eu coloquei um exemplo completo de listagem de arquivos considerando estas questões que comentei, só para ilustrar os conceitos. Repare que os resultados encontrados são guardados em um array para que o garbage collector do eScript tenha oportunidade de se livrar logo da memória alocada para os objetos e kernel32.dll.

    try {

        var blob = new blobDescriptor();
        var fileData;
        var hFind;
       
        blob.dwFileAttributes = SWORD32;
        blob.ftCreationTime = SWORD32;
        blob.ftCreationTime_ = SWORD32;
        blob.ftLastAccessTime = SWORD32;
        blob.ftLastAccessTime_ = SWORD32;
        blob.ftLastWriteTime = SWORD32;
        blob.ftLastWriteTime_ = SWORD32;
        blob.nFileSizeHigh = SWORD32;
        blob.nFileSizeLow = SWORD32;
        blob.dwReserved0 = SWORD32;
        blob.dwReserved1 = SWORD32;
        blob.cFileName = 1000000;
        blob.cAlternateFileName = 1000000;
       
        var nextIndex = filesToProcess.length;
       
        // brand new start of filesToProcess
        if (typeof nextIndex == 'undefined') {
       
            nextIndex = 0;
            filesToProcess[0] = '';
       
        }
       
        hFind = SElib.dynamicLink("Kernel32.DLL", "FindFirstFileA", STDCALL, filenameToMatch, blob, fileData);

        //foi encontrado um arquivo de acordo com o padrão informado
        if (hFind != -1) {

            filesToProcess[nextIndex] = removeNul(fileData.cFileName.toString());

            while (SElib.dynamicLink("Kernel32.DLL", "FindNextFileA", STDCALL, hFind, blob, fileData) != 0)
            {
           
             nextIndex++;
            
             filesToProcess[nextIndex] = removeNul(fileData.cFileName.toString());            
           
            }
               
            return true;
        }
    }
    catch(e) {
   
        throw(e);
       
    }
    finally {
   
        nextIndex = null;
        fileData = null;
   
    }
}


Leia e escreva em Unicode

Um dos modos disponíveis para a função Clib.fopen é de leitura e escrita em Unicode através da letra "u" especificada na chamada da Clib.fopen.
Trabalhar neste modo é a melhor garantia que você pode ter de conseguir exportar ou importar dados de forma portável entre sistemas, principalmente se o banco de dados do Siebel estiver codificado em Unicode (o que, atualmente, é bem possível). Trabalhar com Unicode vai evitar dores de cabeça futuras com acentos e outras mazelas associadas com diferentes codificações de caracteres.

quinta-feira, 1 de março de 2012

Mais alguns truques com o srvrmgr

A um tempo atrás eu havia postado um artigo (Fazendo melhor uso do srvrmgr.exe) sobre o srvrmgr mas faltou falar sobre a manipulação de saída de dados, incluindo um truquezinho sujo que não está documentado. Vamos a eles.

Manipulando a saída dos comandos

É possível alterar a forma como o srvrmgr irá exibir a saída dos programas. Por exemplo, a saída padrão do comando list comp é está aqui:

srvrmgr> list comp

SV_NAME     CC_ALIAS                       CC_NAME                                   CT_ALIAS  CG_ALIAS      CC_RUNMODE   CP_DISP_RUN_STATE  CP_NUM_RUN_  CP_MAX_TASK  CP_ACTV_MTS  CP_MAX_MTS_  CP_START
_TIME        CP_END_TIME  CP_STATUS  CC_INCARN_NO  CC_DESC_TEXT
----------  -----------------------------  ----------------------------------------  --------  ------------  -----------  -----------------  -----------  -----------  -----------  -----------  --------
-----------  -----------  ---------  ------------  ------------
siebsrvdev  BusIntBatchMgr                 Business Integration Batch Manager                  EAI           Batch        Online             0            20           1            1            2012-03-
01 14:59:11
siebsrvdev  BusIntMgr                      Business Integration Manager                        EAI           Batch        Online             0            20           1            1            2012-03-
01 14:59:11
siebsrvdev  ClientAdmin                    Client Administration                               System        Background   Online             0            1                                      2012-03-
01 14:59:11
siebsrvdev  CommConfigMgr                  Communications Configuration Manager                CommMgmt      Batch        Online             0            20           1            1            2012-03-

A saída está truncada, mas isto foi proposital: é exatamente desta forma que os dados foram exibidos no meu terminal. Se você usa isto com frequência provavelmente fica meio aborrecido com o resultado não muito agradável de ler.

Isso pode ser alterado pelo comando configure list. Vamos a ajuda online dele:

srvrmgr> help configure list
  configure list <ValidListCmd> 
       [show <column_name> [ AS <column_header>]]
       [, <column_name>    [ AS <column_header>]] ....

O comando faz exatamente isto: configura um dos subcomandos disponível para o comando list. Ele permitirá que você configure, por exemplo, quais as colunas você quer que sejam exibidas. Vamos ver como está a configuração atual para o list comp:

srvrmgr> configure list comp
    SV_NAME (31):  Server name
    CC_ALIAS (31):  Component alias
    CC_NAME (76):  Component name
    CT_ALIAS (31):  Component type alias
    CG_ALIAS (31):  Component GRoup Alias
    CC_RUNMODE (31):  Component run mode (enum)
    CP_DISP_RUN_STATE (61):  Component display run state
    CP_NUM_RUN_TASKS (11):  Current number of running tasks
    CP_MAX_TASKS (11):  Maximum tasks configured
    CP_ACTV_MTS_PROCS (11):  Active MTS control processes
    CP_MAX_MTS_PROCS (11):  Maximum MTS control processes
    CP_START_TIME (21):  Component start time
    CP_END_TIME (21):  Component end time
    CP_STATUS (251):  Component-reported status
    CC_INCARN_NO (23):  Incarnation Number
    CC_DESC_TEXT (251):  Component description

Se você incluir a opção show você poderá especificar quais as colunas você quer ver e em qual ordem elas irão ser mostradas:

srvrmgr> configure list comp show SV_NAME(31),CC_ALIAS(21),CC_NAME(76)

srvrmgr> configure list comp
        SV_NAME (31):  Server name
        CC_ALIAS (21):  Component alias
        CC_NAME (76):  Component name

srvrmgr> list comp

SV_NAME     CC_ALIAS              CC_NAME
----------  --------------------  ----------------------------------------
siebsrvdev  BusIntBatchMgr        Business Integration Batch Manager
siebsrvdev  BusIntMgr             Business Integration Manager
siebsrvdev  ClientAdmin           Client Administration
siebsrvdev  CommConfigMgr         Communications Configuration Manager
siebsrvdev  CommInboundMgr        Communications Inbound Manager
...
siebsrvdev  eSitesClinicalObjMgr  eSitesClinical Object Manager (PTB)

52 rows returned.

Agora ficou mais fácil ler!

Mas pode ser que você precise ter todas as colunas e se for o caso, o que fazer?

Aí que vem o truque sujo (até aonde eu sei) não documentado. Lhes apresento o comando set. Com ele você pode exibir ou alterar alguns parâmetros do ambiente do srvrmgr. Vamos ver as opções:

srvrmgr> set
username            sadmin*
service             (null)
enterprise          (null)
app server          (null)
compression         FALSE*
encryption          FALSE*
protocol            TCP/IP*
port                (null)*
cluster             (null)*
name server         (null)*
log filter          (null)
verbose             FALSE
batch               FALSE
header              TRUE
footer              TRUE
ColumnWidth         (null)
delimiter          

Não vou explicar aqui cada um destes parâmetros (até porque isto está documentado). A questão fica por conta do parâmetro ColumnWidth. Se você habilitar ele será possível definir o tamanho das colunas através do comando configure list.

srvrmgr> set ColumnWidth true

srvrmgr> set
username            sadmin*
service             (null)
enterprise          (null)
app server          (null)
compression         FALSE*
encryption          FALSE*
protocol            TCP/IP*
port                (null)*
cluster             (null)*
name server         (null)*
log filter          (null)
verbose             FALSE
batch               FALSE
header              TRUE
footer              TRUE
ColumnWidth         1
delimiter  

Pronto! Agora você pode definir o tamanho das colunas! Repare que no comando configure list você vê o nome da coluna e um número entre parênteses: esse número é o tamanho em caracteres da coluna. O comando abaixo de exemplo altera o tamanho de alguns campos do comando list comp para que nome das colunas não seja truncado:

srvrmgr> configure list comp show SV_NAME(31),CC_ALIAS(21),CC_NAME(76),CT_ALIAS(31),CG_ALIAS(31),CC_RUNMODE(31),CP_DISP_RUN_STATE(61),CP_NUM_RUN_TASKS(16),CP_MAX_TASKS(12),CP_ACTV_MTS_PROCS(17),CP_MAX_MTS_PROCS(16),CP_START_TIME(21),CP_END_TIME(21),CP_STATUS(251),CC_INCARN_NO(23),CC_DESC_TEXT(251)

Evitando ter que repetir este trabalho todo

Depois de configurar tantas coisas você deve ter pouca vontade de ter que digitar uma segunda vez isso tudo. para isto existe os comandos save preferences e load preferences. Esses comandos vão, respectivamente, gravar um arquivo de configuração no diretório aonde está localizado srvrmgr e depois carregá-lo. O nome do arquivo é por padrão ".Siebel_svrmgr.pref". Na realidade é um arquivo texto simples. Se depois de você fazer esta configuração você salvar o arquivo e resolver editá-lo, você verá exatamente isto:

configure list components show SV_NAME(31) , CC_ALIAS(21) , CC_NAME(76) , CT_ALIAS(31) , CG_ALIAS(31) , CC_RUNMODE(31) , CP_DISP_RUN_STATE(61) , CP_NUM_RUN_TASKS(16) , CP_MAX_TASKS(12) , CP_ACTV_MTS_PROCS(17) , CP_MAX_MTS_PROCS(16) , CP_START_TIME(21) , CP_END_TIME(21) , CP_STATUS(251) , CC_INCARN_NO(23) , CC_DESC_TEXT(251)

Realmente não é nenhuma mágica, mas funciona.

Conclusão

Talvez você esteja se perguntando para que serventia ter este trabalho todo. Bem, se você estiver escrevendo um parser dos comandos do srvrmgr (como eu estou fazendo aqui) isso é de grande valia!

Falando sério agora, ter a possibilidade de customizar o ambiente do programa para suas necessidades vai lhe ajudar bastante no seu trabalho, principalmente a ganhar agilidade para fazer as coisas. Verifique também a possibilidade de criar alias e salvá-los via save preferences, você vai ver que isso vai lhe ajudar bastante!