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.

Nenhum comentário:

Postar um comentário