Uso de Observer no Lumis Portal

Objetivos

  • Compreender conceitualmente o funcionamento de observadores
  • Entender como o Lumis Portal utiliza essa modelagem para tratar eventos de forma assíncrona e quais os principais eventos padrão
  • Aprender a implementar e registrar um observador no Lumis Portal

Referências complementares

  • Cache
  • Javadoc das classes citadas no documento, relacionadas a observadores e eventos.

Conceito

Observer é um design pattern em que objetos chamados observers, ou observadores, “monitoram” outros objetos a fim de identificar mudanças de estado nesses que possam gerar a necessidade de alguma ação por parte do observador.

Na verdade a comunicação parte do objeto que é observado. Cada objeto desse tipo guarda uma lista dos elementos que se registraram como seus observadores e, ao passar por alguma mudança de estado que precise ser notificada, dispara um evento para essa lista, de modo que os observadores possam tomar alguma ação dependendo do tipo de evento disparado.

O diagrama que representa esse modelo está ilustrado abaixo:

image1.png

Mais detalhes e referências sobre o padrão Observer podem ser obtidos em:http://en.wikipedia.org/wiki/Observer_pattern

ou em qualquer material de referência sobre design patterns.

O principal objetivo da utilização desse padrão é simplificar a arquitetura de cenários em que precisamos tratar de forma transparente e assíncrona eventos que tenham ocorrido no Portal.

O Lumis Portal utiliza essa arquitetura para diversas tarefas de manutenção. Além disso, permite a criação e registro de observadores customizados para tratar eventos padrão disparados pelo Portal ou mesmo a criação de mecanismos completos, incluindo a criação e o disparo de eventos próprios customizados. Observadores customizados que sejam registrados no Portal serão notificados de todos eventos disparados através do PortalEventManager, sejam eventos padrão ou eventos customizados que sejam disparados através dessa API.

Por exemplo, a publicação de um conteúdo é um evento que pode precisar ser tratado de várias formas diferentes por componentes diferentes. Além de ser necessário que seja expirado o cache das instâncias de interface em que o conteúdo deve aparecer, é preciso ocorrer uma reindexação da busca para contemplar o novo conteúdo.

Não faria sentido, no que diz respeito à arquitetura, que o gestor de conteúdo possuísse a inteligência de chamar diretamente métodos para a expiração de cache e a reindexação da busca dos componentes responsáveis por cache e busca. Sendo assim a utilização desse padrão é ideal, pois o gestor de conteúdo só precisa se preocupar em disparar um evento indicando que um conteúdo foi publicado. Os componentes responsáveis pela expiração de cache e pela busca, que são observadores desse tipo de evento, são notificados e tratam a publicação da forma adequada a cada um.

Observadores no Lumis Portal

Além da publicação de conteúdo citada no exemplo acima, o Lumis Portal também utiliza esse modelo para tratar alterações estruturais, ciclo de vida da sessão de um usuário e sincronização. Em ambientes clusterizados, o padrão Observer é aplicado para tarefas de sincronização de mensagens e de arquivos que tenham sido enviados para o Portal entre os membros do cluster. A seguir veremos quais são os principais eventos disparados pelo Lumis Portal e os observadores que tratam esses eventos.

Eventos padrão

Todos os eventos no Lumis Portal devem implementar a interface IPortalEvent. A classe AbstractPortalEvent, que implementa essa interface, é utilizada como base para todos os eventos padrão do Portal.

Cada um desses eventos é recebido por observadores que tem funções específicas no tratamento de cada tipo de evento. Eles também podem ser escutados por observadores customizados que estejam registrados no Portal.

A hierarquia desses eventos, bem como uma descrição do que representa cada evento nessa hierarquia, está abaixo:

image2.png
  • AbstractPortalEvent = Base que representa todos os eventos que podem ser disparados no Portal.
  • AbstractTransactionalPortalEvent = Extensão de AbstractPortalEvent, apenas incluindo a possibilidade do evento ser tratado de forma transacional. No seu construtor devem ser informados um objeto que represente a sessão e uma transação. É base para todos eventos transacionais do Portal.
  • PageRenderDataChangedEvent = Evento que indica que ocorreu alguma mudança de estado em um determinado conjunto de páginas.
  • ChannelRenderDataChangedEvent = Evento que indica que ocorreu alguma mudança de estado em um determinado conjunto de canais. É uma extensão de PageRenderDataChangedEvent já que alterações nos canais representam alterações nas páginas associadas.
  • ServiceInterfaceInstanceRenderDataChangedEvent = Evento que indica que ocorreu alguma mudança de estado em um determinado conjunto de instâncias de interface. É uma extensão de PageRenderDataChangedEvent já que alterações em instâncias de interface representam alterações nas páginas associadas.
  • ServiceInstanceRenderDataChangedEvent = Evento que indica que ocorreu alguma mudança de estado em um determinado conjunto de instâncias de serviço. É uma extensão de ServiceInterfaceInstanceRenderDataChangedEvent já que alterações nas instâncias de serviço representam alterações nas instâncias de interface associadas.
  • ContentRenderDataChangedEvent = Evento que indica que ocorreu alguma mudança em um determinado conteúdo. É uma extensão de ServiceInstanceRenderDataChangedEvent já que alterações nos conteúdos de uma instância de serviço representam alterações nessas instâncias de serviço.
  • PersistenceEvent = Evento base para todos os eventos de persistência no Portal.
  • PostAddEvent = Evento disparado após a ação de adicionar uma entidade.
  • PostDeleteEvent = Evento disparado após a ação de remover uma entidade.
  • PostLoadEvent = Evento disparado após a ação de carregar uma entidade.
  • PostUpdateEvent = Evento disparado após a ação de atualizar uma entidade.
  • PreAddEvent = Evento disparado antes da ação de adicionar uma entidade.
  • PreDeleteEvent = Evento disparado antes da ação de remover uma entidade.
  • PreLoadEvent = Evento disparado antes da ação de carregar uma entidade.
  • PreUpdateEvent = Evento disparado antes da ação de atualizar uma entidade.
  • SessionEvent = Evento base para todos os eventos relacionados ao ciclo de vida da sessão de um usuário.
  • SessionExpiredEvent = Evento disparado na expiração da sessão de um usuário.
  • SessionLoginEvent = Evento disparado no login de um usuário.
  • SessionLogoutEvent = Evento disparado no logout de um usuário.
  • BaseNotificationConfig = Deprecated, não é mais utilizado pelo Portal.
  • ClusterMembershipChangedEvent = Evento lançado quando os membros do cluster são alterados a partir da entrada ou saída de um membro.
  • ClusterMessageReceivedEvent = Evento lançado quando uma mensagem é recebida no cluster.
  • FileSystemEvent = Evento base para eventos relacionados a alterações de arquivos no file system.
  • FileSystemAddEvent = Evento lançado quando é adicionado um novo arquivo ao file system.
  • FileSystemDeleteEvent = Evento lançado quando é removido um arquivo do file system.
  • FileSystemUpdateEvent = Evento lançado quando é modificado um arquivo do file system.

Observadores padrão

Os seguintes observadores, por padrão, já vêm registrados e habilitados na instalação do Lumis Portal.

  • ContentIndexerObserver = Escuta os eventos de persistência (de todos os tipos) disparados pelas classes relacionadas com a estrutura Content do Lumis Portal tratando, basicamente, as diferentes ações relacionadas à persistência de conteúdo. Dispara ações de reindexação de conteúdo na busca, sempre que necessário.
  • PageCacheObserver = Escuta os eventos relacionados à alteração das informações de páginas. Dispara, de forma assíncrona, a limpeza de cache das páginas modificadas.
  • ClusterMembershipObserver = Escuta eventos de alteração de membros do cluster e limpa o cache de memória sempre que um evento é recebido, contemplando a entrada desse novo membro.
  • FileSystemObserver = Escuta todos os eventos relacionados a alterações de arquivos no file system e sincroniza essas alterações em todos os membros do cluster.
  • RssObserver = Escuta eventos de modificação de instâncias de serviço e atualiza o XML do RSS caso as modificações nas instâncias de serviço sejam relacionados a conteúdos que devam ser exibidos no RSS.
  • ServiceInterfaceInstanceObserver = Escuta os eventos de modificação em instâncias de interface e limpa o cache dessas instâncias de interface sempre que um evento for disparado.
  • ServiceInterfaceInstanceDeleteObserver = Escuta o evento disparado previamente à remoção de uma instância de interface (ou seja, o evento do tipo PreDeleteEvent disparado pela classe ServiceInterfaceInstanceConfig). Ao detectar o evento remove essa entidade da lista de produtores WSRP.
  • URLConversionCacheObserver = Escuta eventos de pós-atualização ou pós-remoção relacionados a canais, páginas ou Web Resources. Dispara a limpeza de cache da conversão de urls.

Os observadores padrão executam tarefas importantes para o correto funcionamento de diversos mecanismos do Portal e nunca devem ser excluídos ou desabilitados. No entanto, caso seja necessário customizar de alguma forma o tratamento dos eventos padrão disparados pelo Portal ou mesmo disparar novos eventos para tratamento é possível criar e registrar observadores customizados como veremos a seguir.

Desenvolvendo um observador customizado

Em determinados cenários pode ser necessário implementar tratamentos customizados sobre ações realizadas por serviços padrão ou mesmo pelo próprio framework do Lumis Portal.

O interessante do modelo de utilização de eventos e observadores é que com ele é possível realizar esse tipo de implementação apenas conhecendo os eventos existentes e o momento em que eles são lançados. De outra forma não seria possível criar processos que fossem disparados por esse tipo de ação, já que trata-se de código do produto sobre o qual não temos acesso.

Por exemplo, imaginemos o cenário em que seja desejável que qualquer modificação na estrutura de navegação do Portal seja notificada a um grupo de administradores por email. Como estamos utilizando o serviço padrão de Barra de Navegação do Lumis Portal não temos acesso para customização do código criando um novo Process Action, por exemplo. No entanto podemos implementar um observador que escute os eventos de persistência de conteúdo e verifique em qual serviço ocorreu a notificação. Caso seja no serviço de Barra de Navegação ele pode disparar o email conforme desejado.

Poderíamos considerar variações do exemplo citado para, por exemplo, implementar notificações ou outro tipo de processo sempre que fosse detectado o login de um determinado usuário, uma alteração na estrutura de canais e páginas ou o upload de um arquivo para o servidor. Basta para isso que o observador identifique os eventos corretos e trate de forma adequada.

No entanto, a implementação de observadores sobre o Lumis Portal não precisa se limitar a criação de observadores customizados que escutem eventos padrão do Portal para tratamento. Também é possível criarmos mecanismos completos, definindo novos tipos de eventos e os lançando em determinados momentos específicos da nossa implementação. Assim nossos observadores customizados podem tratar esses novos eventos conforme for o cenário, permitindo toda uma arquitetura baseada no padrão Observer.

IPortalEventObserver e AbstractPortalEventObserver

Todo observador no Portal deve implementar a interface IPortalEventObserver. Essa interface é bastante simples, e possui apenas dois métodos, getEventFilter e onEvent. Uma opção para a implementação é estender a classe AbstractPortalEventObserver, que também implementa a interface IPortalEventObserver.

O filtro sobre o tipo do evento

Uma característica desse modelo é que o observador recebe todos os tipos de eventos disparados pelo objeto sendo observado e fica a seu critério tratá-los ou não. A filtragem para tratamento ou não do evento é realizada considerando basicamente a classe que representa o evento identificado e a classe que lançou o evento.

Para isso, toda classe de observador deve implementar o método getEventFilter, presente na interface IPortalEventObserver, que retorna o filtro que deve ser aplicado sobre os eventos lançados, restringindo a apenas aqueles tipos de eventos que precisam ser tratados pelo observador em questão.

A implementação padrão desse método, na classe AbstractPortalEventObserver utilizada como base para os observadores padrão do Portal, não restringe a nenhum tipo de evento específico, permitindo que o observador responda a qualquer evento do Portal.

É importante que esse filtro seja definido de forma consciente, restringindo apenas aos eventos que de fato interessam ao observador em questão, de modo que ele não seja chamado desnecessariamente para qualquer evento que seja disparado.

O tratamento do evento

Uma vez que um evento seja detectado por um observador e passe pelo filtro, é chamado o método onEvent para que o observador execute o tratamento necessário sobre o evento.

Esse método recebe como parâmetro um objeto do tipo IPortalEvent que, como vimos, é implementado por todos os eventos padrão do Portal. A partir desse objeto o observador pode obter as informações que precisa para realizar o tratamento necessário.

Exemplo

Voltemos a considerar o cenário citado mais acima, em que desejamos criar um observador que envie um email sempre que detectar que foi alterada a estrutura de navegação do Portal. O código abaixo é um exemplo de implementação de observador com essa finalidade:


public class EnvioEmailObserver extends AbstractPortalEventObserver {
 
   @Override
   public IPortalEventFilter getEventFilter()
   {
     // Filtra eventos de pós-persistência disparados pela classe ContentVersion
     return and(or(byInstanceOf(PostAddEvent.class),
     byInstanceOf(PostDeleteEvent.class),
     byInstanceOf(PostUpdateEvent.class)),
     byGroups(ContentVersion.class.getName()));
   }
 
   @Override
   public void onEvent(IPortalEvent event) throws PortalException
   {
     // Obtém, do evento, as entidades afetadas (no caso conteúdos)
     Collection<?> entities = ((PersistenceEvent) event).getEntities();
     // Obtém, também do evento, a transação
     ITransaction transaction = ((PersistenceEvent) event).getTransaction();
 
     // Para cada conteúdo referenciado...
     for(Object entity : entities)
   {
     // Confirma se trata-se de uma instância do serviço de barra de navegação
     ContentVersion contentVersion = (ContentVersion) entity;
     String serviceInstanceId = contentVersion.getContentLocale().getContent().getServiceInstanceId();
     ServiceInstanceConfig config = ManagerFactory.getServiceInstanceManager().get(serviceInstanceId, transaction);
     if(config.getServiceId().equals("lumis.service.navigation"))
     {
       // Envia o email, utilizando API, contendo o nome primário do conteúdo modificado
       SendMailManager sendMailManager = new SendMailManager();
       sendMailManager.sendMail(contentVersion.getPrimaryName());
     }
   }
}
}

Temos alguns pontos a destacar nesse exemplo. Em primeiro lugar, repare que o método getEventFilter limita aos eventos lançados pós persistência (seja para adição, edição ou remoção) apenas pela classe ContentVersion. As funções byInstanceOf e byGroups são usadas para criar filtros sobre o tipo do evento e o tipo da classe que lançou o evento, respectivamente. As funções and e or definem operadores lógicos sobre os critérios definidos sobre o tipo do evento e o tipo da classe que lançou o evento, permitindo a criação de critérios mais elaborados.

Em segundo lugar, veja que como estão sendo capturados todos eventos de pós-persistência lançados pela classe ContentVersion, serão tratados eventos de persistência de todos os tipos de conteúdo do tipo Content no Portal. Isso inclui, além do serviço barra de navegação, a grande maioria dos serviços de conteúdo, sejam padrão ou customizados, desenvolvidos no Portal. Sendo assim, é obtido do evento o identificador da instância de serviço do conteúdo e, a partir desse, o identificador do serviço, de modo que seja possível verificar se trata-se de um conteúdo do serviço barra de navegação e só enviar o email em caso positivo.

Por último, é importante frisar que embora o método onEvent receba um objeto do tipo IPortalEvent, genérico para todos eventos do Portal, a partir da conversão desse objeto para um tipo mais específico é possível obter todas as informações necessárias deste. No caso acima, como já se sabe que o evento em questão é um evento de persistência, a conversão de IPortalEvent para PersistenceEvent pode ser feita de forma segura em onEvent, de modo que é possível obter desse tipo de evento a lista de entidades modificadas e a transação envolvendo a persistência.

Criando e lançando um evento customizado

Tipos de eventos customizados devem implementar a interface IPortalEvent. Alternativamente podem também estender a classe AbstractPortalEvent ou sua extensão AbstractTransactionalPortalEvent no caso de ser um evento transacional, sendo que ambas implementam IPortalEvent.

Um evento customizado não precisa necessariamente armazenar nenhuma informação. Como a detecção do evento no observador se dá primariamente pela classe do evento, a classe do evento já pode ser toda informação que um observador precisa para detectar que determinado evento ocorreu e realizar o tratamento de forma adequada. Sendo assim é muito simples criar novos tipos de evento. Em muitos casos basta estender uma das duas implementações citadas acima e lançá-la quando for necessário. Um observador customizado então receberá a notificação e poderá realizar o tratamento.

O trecho de código abaixo ilustra a criação e lançamento de um evento customizado que estende AbstractPortalEvent, utilizando o PortalEventManager.


// Cria o evento customizado
CustomPortalEvent customPortalEvent = new CustomPortalEvent();

// Envia o evento customizado usando o PortalEventManager
ManagerFactory.getPortalEventManager().notifyObservers(customPortalEvent);

// Para o caso de eventos transacionais, seria necessário que o evento estendesse a classe AbstractTransactionalPortalEvent 
// e portanto na sua criação seria necessário informar o objeto com a sessão (sessionConfig) e a transação, como mostra o código abaixo:

// Cria o evento customizado passando sessionConfig e transaction
CustomTransactionalPortalEvent customTransactionalPortalEvent = new CustomTransactionalPortalEvent(sessionConfig, transaction);

// Envia o evento customizado usando o PortalEventManager
ManagerFactory.getPortalEventManager().notifyObservers(customTransactionalPortalEvent);

Registrando um observador customizado no Portal

Os observadores do Portal são administrados na interface de Gerenciador de Observadores, existente no menu Configuração – Eventos do Portal – Observadores. Essa interface está ilustrada abaixo:

image3.png

Nela é possível adicionar um novo observador, informando apenas a classe que representa esse observador e um nome para exibição conforme interface abaixo, chamada a partir do botão Adicionar:

image4.png

No Gerenciador de Observadores também é possível habilitar ou desabilitar e excluir um observador. Um observador que esteja desabilitado irá temporariamente parar de processar eventos, embora continue existindo para o Portal e possa ser novamente habilitado a qualquer momento. Já a exclusão remove o registro do observador do Portal.

O botão Atualizar Padrão restaura a configuração original de observadores do Lumis Portal, mantendo no entanto, observadores customizados que tenham sido registrados no ambiente.

Principais pontos para abordagem prática

  • Implementação e registro de um observer customizado no Lumis, que escute evento padrão

Autor: Jeosadache Galvão (XTI)

Atualizado em 14.02.2012