Ciclo de vida de uma interface DOUI

Objetivos

  • Compreender o DOUI e Content como nada mais do que implementações para interfaces genéricas Lumis que permitem configurações através do arquivo douidefinition.xml.
  • Entender o fluxo macro de chamadas tanto no método *render* como no *processAction* de uma interface DOUI.
  • Dominar a dinâmica envolvendo **sources** e **controles** durante esse fluxo de modo a prever o comportamento desses elementos em diferentes cenários de renderização.

Referências Complementares

  • Ciclo de vida de uma interface genérica
  • DOUI
  • Sources
  • Controles
  • Process actions
  • Estrutura do XML gerado por uma interface

O ciclo de vida de uma interface

É importante que fique claro a que nos referimos quando falamos sobre o ciclo de vida de uma interface. O ciclo de vida de uma interface, no caso, refere-se a todo processamento referente à renderização de uma interface específica existente em uma página, desde o momento em que é feita a chamada HTTP ao servidor até o momento em que a interface encontra-se renderizada e é exibida ao usuário, tendo opcionalmente passado também pela etapa de process action.

Ou seja, estaremos nos concentrando na renderização de uma interface pontualmente, no caso uma interface DOUI, abstraindo do processo que ocorre para renderizar a página como um todo e que chama a renderização de cada interface existente. Abaixo uma visão geral do processo destacando as chamadas para os métodos *render* (para as interfaces A e B) e *processAction* (apenas para a interface B), que serão o nosso foco nessa análise.

image001.png

Interface DOUI versus Interface Genérica

O diagrama acima é válido para a renderização de uma página, quaisquer que sejam os tipos das interfaces instanciadas, já que toda interface Lumis precisa implementar a interface Java IServiceInterface e, portanto, possuir os métodos **render** e **process action**.

As interfaces DOUI não são exceção. Assim como para interfaces Lumis genéricas, os métodos chamados durante o fluxo de renderização de uma página são o método render para obter o trecho de HTML referente a essa interface e, opcionalmente, o método processAction caso tenha sido disparada o process action dessa interface.

É muito importante que se tenha a visão clara de que um serviço DOUI e, mais especificamente no nosso caso, as interfaces DOUI não são tratadas pelo Portal de forma diferente de qualquer outra interface, seja ela uma interface genérica, DOUI, Content ou de qualquer outro tipo customizado. Ou seja, embora o DOUI seja desenvolvido para rodar sobre o Lumis Portal não existe nenhuma dependência no sentido contrário, ou seja, além do fato de as interfaces implementarem IServiceInterface, o Portal não precisa ter nenhum conhecimento sobre a forma de funcionamento das interfaces DOUI para renderizá-las corretamente na página.

Um serviço que define um framework

Uma questão que surge naturalmente quando analisamos a forma como o Portal enxerga o DOUI é que ele é tratado como um serviço qualquer aos olhos do Portal, embora nos seja apresentado como um framework para criação de serviços.

Em última análise o DOUI é, de fato, um serviço. No entanto trata-se de um serviço altamente configurável, seja na definição de onde obter os dados, na definição de quais elementos (no caso controles) serão apresentados na tela e de que forma ou na definição de quais processos poderão ser disparados pelo usuário, todas configurações realizadas no arquivo douidefinition.xml.

É essa característica de configurabilidade do DOUI, essa possibilidade de reaproveitamento e parametrização de elementos genéricos que o define como um framework, capaz de produzir inúmeras possibilidades de serviços completamente diferentes e bastante específicos.

A possibilidade de desenvolver serviços complexos através de configurações realizadas no arquivo douidefinition.xml, muitas vezes sem precisar sequer escrever código Java, é o que torna o DOUI uma ferramenta poderosa para criação de serviços e às vezes pode nos fazer esquecer de que ele é também, essencialmente, um serviço.

Fluxo Macro

Render

O método *render* de uma interface é aquele responsável por, no final do processamento, disponibilizar no objeto *response* o trecho de HTML relativo à interface para ser incorporado ao HTML final da página.

No caso de uma interface genérica extremamente simples, estilo “Hello World”, o método render poderia ser como no exemplo abaixo:

public void render(IServiceInterfaceRenderRequest request, IServiceInterfaceRenderResponse response)
    throws ServiceInterfaceException, PortalException
{
    try
    {
        response.getWriter().print("Hello World!");
    }
    catch(Exception e)
    {
        throw new UnexpectedException(e);
    }
}

Já a implementação desse método no caso de uma interface DOUI, conforme definido na classe DouiServiceInterface, é bem mais complexa, dividida em subetapas bem definidas, conforme vemos abaixo:

public void render(IServiceInterfaceRenderRequest request, IServiceInterfaceRenderResponse response) throws ServiceInterfaceException, PortalException
{
    try
    {

        ITransaction transaction = PortalTransactionFactory.createTransaction();

        try
        {
            transaction.begin();

            // initialize douiContext
            DouiContext douiContext = getContext(request, response, transaction);

            initDouiContext(douiContext);

            // load controls
            loadFromRequest(douiContext);

            // validate controls
            validate(douiContext);

            // load sources
            loadSources(douiContext);

            // render data
            renderData(response, douiContext);

            transaction.commit();
        }
        catch (Exception e)
        {
            transaction.rollback();
            throw e;
        }
        finally
        {
            transaction.dispose();
        }
    }
    catch (PortalException e)
    {
        throw e;
    }
    catch (Exception e)
    {
        throw new UnexpectedException(e);
    }
}

Podemos reparar que no início do processo de renderização é criada uma transação que é passada ao longo de todas as subetapas do fluxo. Essa é a transação que é recebida por parâmetro em todos os métodos que implementamos sempre que estamos desenvolvendo código para algum elemento DOUI, seja um dataProvider, um controle, um process action, ou qualquer outro. Com isso fica claro que cada interface é renderizada sob uma transação, que é criada e finalizada dentro do escopo do método *render* dessa interface.

O processo se inicia criando e inicializando o objeto douiContext, que como vemos também é passado para todas as subetapas. Mais abaixo veremos do que é composto esse objeto e como ele é utilizado nas etapas do processo.

Na sequência existe uma etapa em que os controles podem carregar seus valores a partir de parâmetros do request, seguida da etapa em que opcionalmente eventuais controles de validação existentes na interface validam seus dados.

Só após essa etapa os sources são carregados. Reparem que a etapa de carregamento dos sources ocorre após os controles terem tentado carregar seus valores do request visto que com esses valores no request os controles podem influenciar o carregamento dos sources, por exemplo definindo filtros nas consultas ou paginação de resultados. O carregamento dos sources, por sua vez, também pode modificar o valor dos controles nessa etapa, como veremos mais para a frente.

Uma vez que os sources e controles estejam carregados chegamos à etapa em que de fato é gerado o HTML de renderização da interface. Nessa etapa cada controle será chamado para retornar um fragmento de HTML, considerando seus valores atuais, tenham sido eles obtidos do request ou do source. Uma vez que todos os controles tenham sido chamados temos como resultado o HTML gerado para a interface que é retornado para o Portal compor com o restante do HTML da página.

É importante ressaltar que qualquer que seja o tipo da interface DOUI, seja ela uma interface genérica (*lum_doui*), uma interface de lista (*lum_douiList*), uma interface de lista administrativa (*lum_douiAdministrationList*) ou de qualquer outro tipo específico, se ela for DOUI, as subetapas da renderização são as mesmas citadas acima. A diferença entre esses diferentes tipos de especificação de interfaces DOUI se dá principalmente nos valores default definidos para alguns elementos da interface, como controles e process actions, mas o fluxo de renderização é o mesmo.

Process Action

O método *processAction* que, como vemos no fluxo de renderização de uma página, quando ocorre, ocorre sempre antes da etapa de renderização da interface, na verdade é muito parecido com o método render quando consideramos as subetapas envolvidas. Abaixo vemos como é a implementação do método processAction de uma interface DOUI:

public void processAction(IServiceInterfaceActionRequest request, IServiceInterfaceActionResponse response)throws ServiceInterfaceException, PortalException
{
    try
    {

        ITransaction transaction = PortalTransactionFactory.createTransaction();

        try
        {
            transaction.begin();

            // initialize contentContext
            DouiContext douiContext = getContext(request, response, transaction);

            initDouiContext(douiContext);

            // load controls

            loadFromRequest(douiContext);

            // validate controls

            validate(douiContext);


            // process actions
            processActions(douiContext);

            transaction.commit();
        }
        catch (Exception e)
        {
            transaction.rollback();
            // if the exception was not a PortalException, log it as an error
            if(!(e instanceof PortalException))
                logger.error("Exception thrown during process action", e);


            // keep original request parameters

            response.setRenderParameters(request.getParameterMap());
            // set render parameter with error message
            String msg = e.getLocalizedMessage();
            if(msg ==null|| msg.length() == 0)
                msg = ManagerFactory.getLocalizationManager().localize("STR_UNEXPECTED_EXCEPTION", new PortalStringResource());
            response.setRenderParameter(DouiContext.DOUI_PARAMETER_ERROR, msg);
        }
        finally
        {
            transaction.dispose();
        }
    }
    catch (PortalException e)
    {
        throw e;
    }
    catch (Exception e)
    {
        throw new UnexpectedException(e);
    }
}

Assim como no método *render*, em um process action é criada uma transação que é passada por todas as subetapas. Da mesma forma também é criado e inicializado o objeto douiContext e os controles tentam carregar seus valores do request.

Já na etapa de validação de controles, como veremos em mais detalhes abaixo, a validação server side sempre ocorre em um process action, enquanto na etapa de renderização é preciso que seja definido explicitamente que ela deve ocorrer.

A partir daí a implementação dos dois métodos é diferente. Enquanto no *render* temos as etapas de carregamento dos sources e geração do HTML renderizado para a interface, no process action temos a etapa em que as classes responsáveis pelos process actions disparados são chamadas.

Essa análise elimina uma dúvida comum. Como sabemos, durante a renderização de uma página, apenas um process action, de uma interface, é chamado. No entanto, quando implementamos uma interface DOUI, temos a possibilidade de configurar diversos process actions com o mesmo id em uma mesma interface, de modo que várias classes sejam chamadas em sequência quando o process action com esse id for disparado.

O que acontece é que, embora de fato o Portal faça apenas uma chamada para o método *processAction* da classe que representa a interface DOUI, a implementação desse método dá suporte para que, através da configuração realizada no douidefinition.xml, sejam chamadas diversas classes de process action a partir da chamada à classe que representa a interface.

Após essas chamadas aos process actions da interface a transação é finalizada e o fluxo se encerra. Caso seja detectado que foi lançada uma exceção, além de ser feito o rollback da transação, são colocados no objeto response parâmetros para que o erro detectado seja tratado na próxima chamada ao método *render*. Além de criar um parâmetro com a mensagem de erro para que essa mensagem possa, por exemplo, ser exibida para o usuário final no próximo ciclo de renderização, são mantidos os parâmetros previamente existentes no request para que a interface mantenha o o seu estado na próxima renderização.

Dessa forma, por exemplo, se um usuário estiver preenchendo um formulário e ocorrer alguma exceção durante a persistência dos dados, a mensagem de erro poderá ser exibida para ele e os valores preenchidos no formulário serão mantidos para que ele possa corrigir o problema e tentar a persistência novamente sem necessitar preencher novamente todos os valores.

O objeto DouiContext

A partir do objeto DouiContext é possível obter todas as informações relacionadas ao contexto onde ocorre o ciclo de vida de uma interface DOUI, direta ou indiretamente.

Algumas informações mais específicas sobre o ciclo de vida, como por exemplo o resultado da validação realizada, a definição da interface em questão e os parâmetros do request na última renderização (utilizados para comparação com os valores da renderização atual para definição do comportamento de controles e sources) são armazenadas diretamente no objeto. Outras podem ser obtidas a partir de objetos para os quais o douiContext guarda referências.

É possível obter, a partir do douiContext, referência para os objetos request, response e para a transação criada no início do fluxo de renderização. Todo resultado que a interface precisa gerar para a renderização é aplicado pelos controles sobre o objeto response que é obtido a partir do douiContext.

Já o objeto request possui informações sobre a interface em questão e a estrutura onde ela se encontra como os identificadores da página, do serviço, da instância de serviço, da interface e da instância de interface. Além disso, também possui todas as informações relacionadas à chamada, como os cookies, modo de renderização (dentro ou fora do F12), parâmetros enviados na chamada, etc.

Além disso, a partir do douiContext são criados, e nele são armazenados, objetos que encapsulam as principais informações e referências sobre os principais elementos DOUI, os **sources**, **controles** e **process actions**. Os objetos em questão são, respectivamente, o SourceContainer, ControlContainer e ProcessActionContainer.

O SourceContainer contém todas as informações sobre os sources da interface sendo renderizada. A partir dele é possível obter os objetos que representam os sources em si, bem como o XML de definição dos mesmos, um vetor contendo apenas os ids dos sources para simplificar algumas operações, uma referência para o source default da interface além do objeto SourceContext, que armazena informações de contexto de renderização dos sources. Também fazem parte do SourceContainer os métodos utilizados para a inicialização e carregamento dos sources, chamados durante o ciclo de vida pela DouiServiceInterface.

O ControlContainer, por sua vez, possui papel similar no que diz respeito aos controles. Além de permitir a obtenção dos objetos que representam os controles a partir do ID, também possui vetores específicos para armazenar referência apenas para os controles de um determinado tipo, facilitando alguns processos que devem ocorrer apenas sobre esses controles. Isso se aplica para controles de validação, controles raiz da hierarquia e controles que podem ser atualizados via client-side script, além de possuir um vetor que agrupa os controles existentes na interface com base no seu tipo. Assim como no SourceContainer, o ControlContainer possui os principais métodos de operações sobre os controles disparadas ao longo do ciclo de vida da interface. Nesse caso estamos falando da inicialização dos controles, validações de permissão, carregamento dos controles a partir do request, validação (no caso de validadores), geração do XML de renderização e atribuição de parâmetros para process actions.

Por último, o objeto ProcessActionContainer possui referência apenas para os nós de definição XML dos process actions existentes na interface, não armazenando referências para os objetos que representam os process actions, como o SourceContainer e o ControlContainer fazem com sources e controles. Nesse caso, quando um process action precisa ser disparado, o objeto que o representa é criado logo antes da chamada ao método processAction da classe em si. Os métodos chamados pela DouiServiceInterface para essa execução, esses sim, fazem parte do ProcessActionContainer.

Com essa análise, percebemos que o DouiContext possui objetos ou referências para objetos que armazenam praticamente todas as informações necessárias para todas as etapas do ciclo de vida de uma interface DOUI. A compreensão desse objeto, e de onde obter cada informação a partir dele, é fundamental para possibilitar o desenvolvimento de elementos DOUI de forma clara e simples.

Fluxo Detalhado

Nessa seção detalharemos as subetapas existentes nos métodos *render* e *processAction* de uma interface DOUI.

Obtenção do DouiContext

Nessa etapa é criado o objeto DouiContext. Como parâmetros são passados os objetos request, response e transaction para que sejam criadas e armazenadas referências para esses objetos dentro do DouiContext.

Durante a criação, é verificado se essa renderização foi ocasionada pela submissão de um formulário (postBack). Também é verificado se foi disparado process action. O resultado dessas duas verificações pode ser obtido a partir do DouiContext pelos métodos *isPostBack* e *isProcessAction*, respectivamente.

Em seguida são armazenados, obtidos do request, os parâmetros da última renderização. Esses parâmetros são utilizados na etapa de carregamento de sources e controles para definir alguns comportamentos de renderização.

Por último é obtida e armazenada a definição DOUI da interface. Nesse momento é feito o merge da definição global dos sources com a definição específica da interface, de modo que o XML armazenado já possui a definição final que deve ser aplicada.

Inicialização do DouiContext

Nesse momento basicamente são criados e inicializados os objetos SourceContainer e ControlContainer.

Na criação do SourceContainer é criado o SourceContext e são obtidos os nós de definição dos sources a partir do nó de definição da interface existente no DouiContext. Além disso é armazenada uma referência para o objeto DouiContext. Em seguida, na sua inicialização, são identificados todos os sources presentes na definição e os objetos que representam esses são criados e incluídos nos vetores que armazenam esses objetos.

Já na criação do ControlContainer a única ação é o armazenamento da referência para DouiContext. Em seguida, na inicialização, é verificado se trata-se de uma interface administrativa do Portal e, em caso positivo, é adicionado um controle específico para inclusão do estilo padrão de renderização do Lumis Portal para esse tipo de interface.

Na sequência, a partir do nó de definição de controles da interface, também obtido a partir do nó de definição da interface existente em DouiContext, são identificados os controles raiz da interface e é disparado o método para criação desses existente na classe *ControlFactory*. Esse método é recursivo, de modo que só precisa ser chamado sobre os controles raiz para que todos os controles da interface sejam criados. Durante a criação de um controle é identificado se esse possui subcontroles e nesse momento eles também são criados.

Por último, é adicionado um controle de sumário de erro, caso não exista nenhum na definição original da interface. Esse é um controle obrigatório para o funcionamento do tratamento de erro em DOUI, por isso sempre deve existir.

Carregamento dos controles a partir do request

Aqui os controles verificam se existem parâmetros no request que eles devam usar para definir seus valores. A classe *DouiServiceInterface* chama o método do *ControlContainer* que dispara essa verificação a partir dos controles raiz da interface.

Para cada controle, primeiramente é verificado se ele implementa a interface IDataControl, já que apenas controles que implementem essa interface possuem a característica de manter um estado baseado em valores obtidos do request. Em caso negativo, o método é chamado recursivamente sobre os seus filhos, já que um controle que não é *DataControl* pode possuir como filho um controle *DataControl*.

Caso se trate de um controle que implemente IDataControl, simplesmente é chamado o método *loadFromRequest* do controle. O que o controle fará, a partir daí, depende da implementação dele.

No caso da implementação padrão de *DataControl*, primeiramente é chamado um método para recursivamente tentar obter do request o valor para os seus controles filhos. A recursão aqui se dá de forma muito similar à recursão descrita acima.

Em seguida, após ter carregado o valor dos filhos do request, é que de fato o controle em questão tentará obter seu valor do request. Existem duas situações nas quais o controle atribuirá o seu valor a partir de um parâmetro do request.

A primeira situação depende de três condições. O valor do controle não ter sido previamente atribuído, normalmente através de um atributo “value” presente na definição do controle, estarmos em um cenário de postBack e existir no request um parâmetro cujo nome seja igual ao id do controle. Nesse caso, o valor desse parâmetro será atribuído como valor do controle.

A segunda situação depende de quatro condições. O valor do controle não ter sido previamente atribuído, assim como acima, não estarmos em um cenário de postBack, o controle possuir em sua definição o atributo “requestParameterName” configurado e, por último, existir no request um parâmetro cujo nome seja igual ao valor do atributo “requestParameterName”. Nesse caso, o valor do parâmetro também será atribuído como valor do controle.

Podemos tirar algumas conclusões interessantes a partir da análise de como os controles obtêm seus valores do request. Em primeiro lugar, que isso só ocorre quando o controle não tem seu valor previamente atribuído, ou seja, a utilização do atributo “value” na definição do controle evita que ele carregue seu valor do request qualquer que seja a situação.

Em segundo lugar, podemos ver que o atributo “requestParameterName” só tem utilidade em renderizações que não sejam postBack. Essa implementação assume que, em um postBack, o controle já teve a chance de submeter seu valor atual e portanto busca apenas o parâmetro cujo nome seja igual ao id do controle. Essa é uma informação valiosa para a implementação de controles em cenários complexos de renderização em DOUI.

Abaixo temos um diagrama que ilustra o fluxo citado para essa etapa da renderização. Nesse cenário estamos considerando uma interface com dois controles raiz, A e B, sendo que cada um possui um subcontrole, SA e SB, respectivamente. Todos são do tipo DataControl, exceto o controle B, que é do tipo Control. Nesse exemplo os controles A e SB possuem parâmetros no request que devem utilizar para definir seu valor.

LoadFromRequest.jpg

Validação dos controles

Na etapa de validação de controles basicamente é chamado o método, existente no *ControlContainer*, que realiza a validação de cada controle validador presente na interface e atribui o resultado dessa validação, um valor booleano, em uma variável da classe DouiContext para indicar se a validação foi bem sucedida ou não.

O método que realiza a validação de todos os controles inicialmente verifica se a validação deve ser executada. Existem duas condições para isso ocorrer, ou estarmos em um process action ou existir no request um parâmetro (doui_validate) com o valor “true”, indicando que a validação deve ocorrer também na etapa de render. Esse parâmetro é colocado no request durante a etapa de process actions caso seja identificado algum problema na validação. O objetivo é que, na renderização subsequente à etapa de processamento em que a validação falhou, ela ocorra novamente para que a mensagem de erro possa ser exibida para o usuário e o problema corrigido.

No caso de a validação ser necessária, para cada controle de validação existente, que nesse momento são obtidos do vetor que guarda os controles desse tipo, é chamado o método *serverValidate*, responsável pela validação server-side do controle. Apenas no caso de todos eles indicarem a validação como bem sucedida, a validação da interface como um todo é também considerada bem sucedida. No caso de algum controle não passar na validação será lançada uma exception que interromperá o fluxo e exibirá uma mensagem para o usuário.

Carregamento dos sources

Esse é o momento em que os sources serão carregados a partir de seus data providers. Repare que essa etapa só ocorre após os controles terem se carregado a partir do request pois é nesse momento que os controles têm a oportunidade de atribuir parâmetros de source através do método *setSourceParameters*, que podem influenciar o carregamento do source. Um exemplo típico é o de uma lista filtrada, onde um controle de filtro atribui um parâmetro para o source que popula a lista com o termo que precisa ser considerado para a filtragem.

Para iniciar o processo, a classe *DouiServiceInterface* apenas chama o método *loadSources*, existente no *SourceContainer*. Inicia-se então um processo recursivo em que os sources serão carregados. A recursão nesse caso existe pelo fato de que o carregamento de um source pode, indiretamente, ocasionar a necessidade de recarregamento de outros sources, e alguns ciclos podem ser necessários até que fique estável.

Inicialmente, o *SourceContainer* obtém a lista de sources da interface e passa por ela verificando se algum source está marcado como necessário para ser carregado. Caso identifique ao menos um, a lista toda é novamente processada e todos os sources marcados para carregamento são carregados. Ao término do processamento da lista, a função é chamada recursivamente para que seja verificado se nesse último ciclo, o carregamento de algum source pode ter gerado a necessidade de carregamento de outro que já não tenha sido carregado. Nesse caso o ciclo se repete.

O que acontece é que sources e controles respeitam o padrão de observadores, em que os controles são os observadores e os sources são observados. Na inicialização de um controle, caso ele seja associado a um source, ele se registra neste de modo que receba notificações do source no caso de mudanças. Assim, sempre que um source tem seu valor alterado, ele dispara uma notificação informando isso aos controles, pois os controles associados a esse source podem precisar recarregar seu valor.

O controle, por sua vez, também pode precisar atribuir parâmetros de source, quando tem seu valor alterado. Sendo assim, uma modificação em um source pode gerar a necessidade de um controle recarregar o seu valor o que pode atribuir parâmetros a outro source, que também precisará ser recarregado e pode gerar a necessidade de recarregar outros controles. O ciclo pode se repetir algumas vezes até que todos os controles estejam com seus valores finais, tenham atribuído todos os parâmetros de source necessários e todos os sources tenham se carregado também considerando os parâmetros finais.

Já o processo de carregamento de um source propriamente dito depende da implementação do método *load* da classe de source sendo utilizada. Para a implementação padrão, o processo consta basicamente de chamada ao data provider para trazer os dados, chamada aos post-load processors (caso existam).

No final o source é marcado como modificado e os observadores, no caso os controles que estejam registrados nesse source, são notificados da mudança. Essa notificação é que, como vimos acima, pode fazer com que controles atualizem seu valor e atribuam parâmetros a outros sources, que então ficarão marcados para serem recarregados e o ciclo se repete.

Um cenário típico de dependência entre sources através de controles é o de filtros. Por exemplo, imaginemos o cenário de uma tabela que pode ser filtrada por dois campos, UF e cidade. Os filtros são dependentes de modo que, após selecionar uma UF, queremos que sejam exibidas apenas as cidades daquela UF na combo para filtragem.

Nesse caso, o controle de filtro que exibe as opções de UF depende do carregamento do source de UF e ele, por sua vez, atribui um parâmetro para o source de cidades ser carregado. Nesse caso a recursão é necessária para garantir que os sources e controles sejam carregados corretamente.

Abaixo temos um diagrama do fluxo para essa etapa. Nesse exemplo consideramos uma interface com dois sources, A e B e um controle do tipo *DataBoundControl*. O controle é associado ao source B, de modo que esse está marcado para ser carregado de início, enquanto que o source A, que não possui controles associados, não está marcado para ser carregado. Como o controle está associado ao source B, ele recebe as notificações enviadas por esse source. O controle, no entanto, atribui parâmetros ao source A.

image3.jpg

Renderização dos dados

Ao chegarmos nessa etapa todos os elementos já estão prontos para a geração dos dados que serão utilizados na renderização da interface. Nesse momento os sources já foram populados considerando os parâmetros atribuídos a si, os controles já têm seus valores definidos, sejam valores obtidos dos sources ou do request e já foram realizadas as validações necessárias. Basta agora que os controles montem o XML com os seus dados para que seja transformado no trecho de HTML que representará a interface em questão.

O método *renderData* da classe *DouiServiceInterface* é o responsável por realizar as chamadas sobre o response para que seja realizada a transformação do XML que será gerado com o XSL que está aplicado na interface e para retornar de fato o resultado dessa transformação (já um HTML) para o browser. Entre uma chamada e outra é montado o XML.

Inicialmente é gerado o nó *douiContext*. Nesse trecho do XML são disponibilizadas para a interface algumas das informações mais comumente utilizadas para a renderização que fazem parte do objeto *DouiContext*. As informações aqui existentes podem ser classificadas em três tipos:

  • Informações estruturais: Identificador da instância de serviço, identificador da instância de interface, identificador da página, tipo de layout definido para a página e identificador do canal.
  • Informações sobre o contexto de renderização: Flag indicando se a página está sendo impressa, idioma no qual está sendo realizada a navegação, flag indicando se renderização está ocorrendo em modo de debug e modo de renderização (dentro ou fora do F12).
  • Informações sobre o usuário: Identificador, nome, email e login.

Essas informações ficam assim disponíveis, de modo que o xsl pode utilizá-las para a renderização. Exemplos típicos são interfaces que exibem o nome do usuário (por exemplo para exibir uma mensagem do tipo “Bem vindo XXX”), interfaces que verificam o tipo de layout definido para montar o seu html tableless ou com tabelas.

Em seguida, a classe *DouiServiceInterface* chama o método *getRenderData* do *ControlContainer*, que retornará o XML gerado pelos controles para, junto com o nó *douiContext*, ser transformado pelo XSL e retornado ao browser.

Para a geração desse bloco do XML é necessário que seja chamado o método *setRenderData* sobre cada controle existente na interface para que cada um gere o seu XML internamente. Essas chamadas se dão através de um processo recursivo, inicialmente aplicado sobre os controles raiz da interface e que, através da recursão, se estende para os controles filhos de cada controle. Terminada essa etapa, todos os objetos que representam os controles já estão modificados com o XML final.

Em seguida o *ControlContainer* se encarrega de gerar um container para os controles configurados para atualização via client-side script. Para isso simplesmente é chamado o método *createContainer* de cada controle configurado para esse tipo de renderização.

Em seguida é verificado se a renderização atual deve trazer a interface inteira ou se foi disparada a renderização pontual de um controle. Esse é um recurso do Lumis Portal que permite que apenas um controle da interface seja atualizado a partir de uma chamada Ajax, mantendo todo o restante da interface inalterada e otimizando o tempo de processamento. Como vemos aqui, mesmo para a renderização de um controle pontualmente é necessário que sejam executadas todas as etapas anteriores, de modo que o controle seja renderizado corretamente sobre o contexto em que se encontra.

No caso de se tratar da renderização pontual de um controle, o *ControlContainer* irá identificar o controle em questão e obterá apenas o XML deste, ao invés de obter de todos os controles da interface. Caso contrário, ele irá obter o XML gerado dos controles raiz da interface, concatenando o resultado na ordem em que os controles aparecem na interface. Como o XML de definição de um controle inclui a definição de seus controles filho, o resultado dessa concatenação já contém todo o XML dos controles da interface. Com isso o XML da interface está pronto e é escrito no response para transformação XSL.

Abaixo temos um diagrama do fluxo para essa etapa. Nesse cenário possuímos uma interface com dois controles raiz, A e B, sendo que A possui um subcontrole, SA.

image4.jpg

Processamento

Essa etapa, presente apenas no fluxo de process action, é o momento em que eventuais process actions que tenham sido disparados serão executados.

Em primeiro lugar, a classe *DouiServiceInterface* verifica o resultado da etapa de validação de controles. Caso tenha ocorrido algum problema na validação o process action disparado não é executado. Nesse caso, é atribuído um parâmetro no response indicando que a validação deve ocorrer novamente na etapa de renderização seguinte, permitindo que os erros de validação sejam exibidos ao usuário para tratamento dos problemas. Caso a validação tenha sido bem sucedida, é chamado o método *processActions* do *ProcessActionContainer*.

Nesse método, o *ProcessActionContainer* obtém do request o parâmetro com o identificador do process action que deve ser executado e, caso esse parâmetro exista, ele obtém todos os nós de process actions com esse identificador da definição de process actions da interface.

Então, na ordem em que estão definidos na interface, os objetos que representam esses process actions são criados, inicializados, e, para cada um, são atribuídos os parâmetros dos controles. Essa atribuição de parâmetros é realizada através de chamada ao *ControlContainer*, que por sua vez faz uma chamada ao método *setProcessActionHandlerParameter* de cada controle da interface de forma recursiva a partir dos controles raiz.

Finalmente, para cada process action que deve ser processado é chamado o método *processAction* da classe que o representa, em que o processo é de fato executado.

Caso nenhum process action tenha sido disparado, a classe *DouiServiceInterface* simplesmente obtém os parâmetros definidos no request e os coloca no response, de modo que sejam mantidos para a próxima etapa de renderização.

Abaixo temos um diagrama do fluxo para essa etapa. Nesse exemplo estamos considerando uma interface com dois process actions, A e B, com o mesmo ID, de modo que os dois devem ser disparados em sequência. A validação foi bem sucedida.

image5.jpg

Principais pontos para abordagem prática

  • Demonstração de análise do fluxo, através de depuração de um cenário de erro
  • Desenvolvimento de cenário de controle lendo dados ora do source, ora do request, e disparando o carregamento de outros sources

Autor: Tiago Porcher (Lumis)