Posts tagged ‘Seam’

Identity Management de verdade com novo Seam 2.1 Beta

Poxa vida, tava preparando um post sobre RecordProcessor, e sai a versão nova do Jboss Seam ontem, e com ela um novo Identity Manager, e desta vez com suporte a LDAP!!!!
Bem, desta vez vou dividir em mais partes o post, nesta primeira parte vou focar apenas na configuração do Apache Directory Server (LDAP) e na autenticação com seam.

Pré requisitos:

Configurando o LDAP

Bem, o primeiro passo é criar uma aplicação com Seam, usando o bom e velho seam-gen. Veja este post para maiores informações.
Instale o Apache Directory Studio usando o update-site para eclipse e marque as opções Studio e Directory Server (isso vai lhe permitir iniciar um servidor LDAP de dentro do eclipse)

Bem, com o plugin configurado, abra a perspectiva de Servers do Directory Studio e crie um novo servidor, assim como na figura abaixo:

Servidor de LDAP do Directory Studio (clique para fullsize)

Servidor de LDAP do Directory Studio (clique para fullsize)

Inicie o servidor, clique com o botão direito sobre o mesmo, e então LDAP Browser -> New Connection. Isto vai conecta-lo ao servidor de LDAP, na aba esquerda “LDAP Browser” você pode agora criar os usuários e roles.

Bem, vamos criar uma árvore onde abaixo de nosso domain (dc), existe uma Organizational Unit (ou) para users e outra para roles.

Clique com o botão da direita em dc=example,dc=com e depois new entry. Selecione uma nova entrada e na tela de object classes “Organization Unit” e Add…

Adicionando uma OU (clique para fullsize)

Adicionando uma OU (clique para fullsize)

Na próxima tela no RDN use ou e roles como na figura abaixo:

Definindo o OU (clique para fullsize)

Definindo o OU (clique para fullsize)

Bem, agora repita este passo e crie uma ou com nome de users.

Bem, agora é preciso criar um usuário, basta seguir as telas abaixo:

Criando um usuário (clique para fullsize)

Criando um usuário (clique para fullsize)

Você deve adicionar os objetos Person e extensibleObject em sua entrada:

Adicionando extensible Object (clique para fullsize)

Adicionando extensible Object (clique para fullsize)

Na próxima tela é importante que seu DN fique da seguinte maneira : uid=$USER,ou=users,dc=example,dc=com pois esta é a string de pesquisa usada pelo componente do Seam no LDAP, então adicione apenas uid no seu RDN como na figura abaixo:

Cuidado com RDN... (clique para fullsize)

Cuidado com RDN... (clique para fullsize)

Na próxima tela você terá que adicionar dois campos a mais que são obrigatórios (CN=Common Name e SN=Surname) adicione um nome e sobrenome. Além disto, você deve adicionar um atributo member que vai apontar para o role que seu usuário participa.

Para adicionar um atributo clique no ícone mostrado na figura abaixo:

Clique no ícone em destaque (segundo da esquerda pra direita)

Clique no ícone em destaque (segundo da esquerda pra direita)

Agora escolha member:

Adicionando um member em nossa entrada (clique para fullsize)

Adicionando um member em nossa entrada (clique para fullsize)

Além disto você vai precisar de um userpassword:

Adicionando atributo userPassword

Adicionando atributo userPassword

No final, sua entrada deve se paracer com a figura abaixo:

Resultado final da entrada de um usuário (clique para fullsize)

Resultado final da entrada de um usuário (clique para fullsize)

Ok, o usuário esta configurado (a propósito, criei o usuário bob com senha bob :P )

Para cada novo role de um usuário, basta adicionar um novo atributo member para o usuário em questão

Agora podemos partir para o seam :)

Configurando o Identity Management

Configura o identityManagement é muito simples. IdentityManagement é uma classe que delega a um IdentityStore (no nosso caso LDAP), então você precisa configura o ldapIdentityStore e um security-identity-manager

Agora basta adicionar o ldapIdentityStore e security-identity-manager:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<security:ldap-identity-store name="ldapIdentityStore"
   			server-address="localhost"
   			server-port="10389"
   			user-DN-prefix="uid="
   			user-DN-suffix=",ou=users,dc=example,dc=com"
   			user-context-DN="ou=users,dc=example,dc=com"
   			user-password-attribute="userPassword"
   			role-context-DN="ou=roles,dc=example,dc=com"
   			role-DN-prefix="cn="
   			role-DN-suffix=",ou=roles,dc=example,dc=com"
   			bind-DN="uid=admin,ou=system"
   			bind-credentials="secret"
   			user-role-attribute="member"
   			user-object-classes="person,uidObject">
   </security:ldap-identity-store>
 
   <security:identity-manager identity-store="#{ldapIdentityStore}"></security:identity-manager>

Pronto, você pode inclusive remover seu authenticator agora :) O Seam cuida desta nova forma de autenticação para você.

Dos descritores acima quero falar apenas de dois pontos (ta tarde pra caralho e quero dormir :|). Bem, o server-port não existe no XSD do seam, parece ser um bug pois o componente tem o atributo e você pode usar, eu já fui fominha e abri um bug mas podem usar sem problemas. Outro ponto é o atributo enabled que quando usado (nao foi nosso caso), deve ter o valor TRUE e não true, sabe por que? Abaixo um trecho do LDAPIdentityStore

431
if (LDAP_BOOLEAN_TRUE.equals(value)) return true;

O valor LDAP_BOOLEAN_TRUE é “TRUE” ou seja, se você usar “true” minúsculo vai levar graxa na cara.

Bom, agora, quando voce acessar sua aplicação e for na tela de login, ja vai estar usando o LDAP. Não precisou mexer em xhtml, nem em classe alguma. Não é uma beleza?

Pessoal, esse post eu fiz questão de varar a noite, primeiro porque só esta feature já é um motivo para vocês usarem Seam, segundo porque sou fominha e queria postar antes de qualquer um :D

O próximo post vou continuar daqui, vou mostrar como usar o IdentityStore para adicionar users via web no LDAP (sem você escrever uma linha de código), como funciona o novo mecanismo de permissions e como mesclar LDAP e banco de dados para armazenar permissions no banco de dados.

Inté

Eu sei o que vocês fizeram no verão passado

Não se assustem! Não se trata de mais um filme de quinta categoria com adolescentes gostosas e sem nenhum sentido do cinema americano.
Vim aqui hoje falar do Envers. O Envers é um dos mais novos frameworks da JBoss a alcançar a maturidade GA (General Availability), a grande sacada do envers é gerar um histórico de todas modificações que ocorrem para uma determinada entidade. Quantas vezes você já não esbarrou em um requisito de projeto onde o cliente solicitava a capacidade de restaurar uma versão do objeto modificado há 3 meses atrás com os valores que este tinha na época? Ou então por motivo de alguma auditoria interna, você precisou guardar esta modificações em uma tabela “histórico”. Bem, eu já passei pelas duas situações, e tenho certeza que muitos de vocês já tiveram o mesmo problema.
Para resolver este problema chegou o Envers. O Envers funciona como um interceptor do Hibernate que atua em toda modificação que é realizada para uma classe anotada para ser versionada. Quando sua Session modifica a classe o envers gera uma entrada em uma tabela de histórico. A configuração é extremamente simples. E o seu uso mais ainda, mas achei que valia a pena fazer um pequeno post sobre ele.
Como todos exemplos do meu blog, vamos iniciar criando um novo projeto seam (consulte posts anteriores e a documentação do Seam para isso)
Uma vez que seu projeto esteja criado. Defina uma nova entidade:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.furiousbob.com;
 
import java.io.Serializable;
 
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
 
import org.jboss.envers.Versioned;
 
@Entity
@Table(name="NEWS")
@Versioned
public class News implements Serializable {
	@Id
	@Column(name="ID")
	@GeneratedValue(strategy=GenerationType.IDENTITY)
	private long id;
 
 
	@Column(name="TITLE")
	private String title;
 
	@Column(name="CONTENTS")
	private String contents;
 
	public long getId() {
		return id;
	}
 
	public String getTitle() {
		return title;
	}
 
	public String getContents() {
		return contents;
	}
 
	public void setId(long id) {
		this.id = id;
	}
 
	public void setTitle(String title) {
		this.title = title;
	}
 
	public void setContents(String contents) {
		this.contents = contents;
	}
}

Note na linha 16 a presença de uma anotação que define que nossa classe é agora versionada. O Envers suporta versionamento de classes completas ou de atributos.

Vá no console do seam, e execute ./seam generate-ui. Isso vai gerar todo o código e interfaces html para nosso CRUD.

Vamos então criar uma nova entidade. Ao clicar em create news, o sistema vai solicitar que você se autentique (admin:admin). Vamos então entrar com algum texto para nossa entidade, conforme a figura abaixo:

Criando uma entidade (clique para fullsize)

Criando uma entidade (clique para fullsize)

Clique em “Done” e você será redirecionado para a tela que apresenta a entidade. Uma das colunas possui uma ação para selecionar a entidade para edição “Select”, clique nesta ação e mude por exemplo o valor da propriedade contents conforme a figura abaixo:

Conteudo alterado de forma indevida (clique para fullsize)

Conteudo alterado de forma indevida (clique para fullsize)

Oops, alguem modificou o conteúdo! Como podemos restaurar a ordem no sistema? Se você estiver usando o Envers a tarefa é simples :). Baixe o envers, adicione o jar ao seu lib. Edite o arquivo deployed-jars-ear.list e adicione a lib do envers. Edite seu persistence.xml de forma que fique parecido com o abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?xml version="1.0" encoding="UTF-8"?>
<!-- Persistence deployment descriptor for dev profile -->
<persistence xmlns="http://java.sun.com/xml/ns/persistence" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" 
             version="1.0">
 
   <persistence-unit name="envers">
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      <jta-data-source>java:/enversDatasource</jta-data-source>
      <properties>
         <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
         <property name="hibernate.hbm2ddl.auto" value="update"/>
         <property name="hibernate.show_sql" value="true"/>
         <property name="hibernate.format_sql" value="true"/>
         <property name="jboss.entity.manager.factory.jndi.name" value="java:/enversEntityManagerFactory"/>
           <property name="hibernate.ejb.event.post-insert"   
             value="org.jboss.envers.event.VersionsEventListener" />  
          <property name="hibernate.ejb.event.post-update"   
             value="org.jboss.envers.event.VersionsEventListener" />  
          <property name="hibernate.ejb.event.post-delete"   
             value="org.jboss.envers.event.VersionsEventListener" />  
 
      </properties>
   </persistence-unit>
 
</persistence>

As linhas 17-22 são as responsáveis por ativar o envers. Se você checar seu SGDB agora, pode notar 2 tabelas novas: NEWS_versions e _revisions_info. Estas são as tabelas criadas automaticamente pelo Envers para você. A tabela _versions, guarda a informação da entidade versionada, é gerada uma para cada entidade anotada, a outra tabela é de controle interno do Envers.

Bem, vamos modificar nosso arquivo NewsList.xhtml (gerado pelo Seam) de forma que agora possamos verificar as versões existentes para cada uma de nossas entidades. A lista abaixo apresenta a nova coluna que inserimos:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
 
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                template="layout/template.xhtml">
 
<ui:define name="body">
 
    <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
 
    <h:form id="newsSearch" styleClass="edit">
 
        <rich:simpleTogglePanel label="News search parameters" switchType="ajax">
 
            <s:decorate template="layout/display.xhtml">
                <ui:define name="label">contents</ui:define>
                <h:inputText id="contents" value="#{newsList.news.contents}"/>
            </s:decorate>
 
            <s:decorate template="layout/display.xhtml">
                <ui:define name="label">title</ui:define>
                <h:inputText id="title" value="#{newsList.news.title}"/>
            </s:decorate>
 
 
        </rich:simpleTogglePanel>
 
        <div class="actionButtons">
            <h:commandButton id="search" value="Search" action="/NewsList.xhtml"/>
        </div>
 
    </h:form>
 
    <rich:panel>
        <f:facet name="header">News search results</f:facet>
    <div class="results" id="newsList">
 
    <h:outputText value="The news search returned no results." 
               rendered="#{empty newsList.resultList}"/>
 
    <rich:dataTable id="newsList" 
                var="news"
              value="#{newsList.resultList}" 
           rendered="#{not empty newsList.resultList}">
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="id #{newsList.order=='id asc' ? messages.down : ( newsList.order=='id desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='id asc' ? 'id desc' : 'id asc'}"/>
                </s:link>
            </f:facet>
            #{news.id}
        </h:column>
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="contents #{newsList.order=='contents asc' ? messages.down : ( newsList.order=='contents desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='contents asc' ? 'contents desc' : 'contents asc'}"/>
                </s:link>
            </f:facet>
            #{news.contents}
        </h:column>
        <h:column>
            <f:facet name="header">
                <s:link styleClass="columnHeader"
                             value="title #{newsList.order=='title asc' ? messages.down : ( newsList.order=='title desc' ? messages.up : '' )}">
                    <f:param name="order" value="#{newsList.order=='title asc' ? 'title desc' : 'title asc'}"/>
                </s:link>
            </f:facet>
            #{news.title}
        </h:column>
        <h:column>
            <f:facet name="header">action</f:facet>
            <s:link view="/#{empty from ? 'News' : from}.xhtml" 
                   value="Select" 
                      id="news">
                <f:param name="newsId" 
                        value="#{news.id}"/>
            </s:link>
        </h:column>
        <h:column>
        	<f:facet name="header">Revisions</f:facet>
        	<s:link view="/newsRevision.xhtml" value="check revisions">
        		<f:param name="id" value="#{news.id}"/>
        	</s:link>
        </h:column>
    </rich:dataTable>
 
    </div>
    </rich:panel>
 
    <div class="tableControl">
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.previousExists}" 
               value="#{messages.left}#{messages.left} First Page"
                  id="firstPage">
          <f:param name="firstResult" value="0"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.previousExists}" 
               value="#{messages.left} Previous Page"
                  id="previousPage">
            <f:param name="firstResult" 
                    value="#{newsList.previousFirstResult}"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.nextExists}" 
               value="Next Page #{messages.right}"
                  id="nextPage">
            <f:param name="firstResult" 
                    value="#{newsList.nextFirstResult}"/>
        </s:link>
 
        <s:link view="/NewsList.xhtml" 
            rendered="#{newsList.nextExists}" 
               value="Last Page #{messages.right}#{messages.right}"
                  id="lastPage">
            <f:param name="firstResult" 
                    value="#{newsList.lastFirstResult}"/>
        </s:link>
 
    </div>
 
    <s:div styleClass="actionButtons" rendered="#{empty from}">
        <s:button view="/NewsEdit.xhtml"
                    id="create" 
                 value="Create news">
            <f:param name="newsId"/>
        </s:button>
    </s:div>
 
</ui:define>
 
</ui:composition>

O que nos interessa aqui são as linhas 87-92, que adicionam uma nova coluna a tabela de listagem de entidades. Após ter feito este passo, sua tela deverá parecer com a tela abaixo:

Nova listagem de entidades (clique para fullsize)

Nova listagem de entidades (clique para fullsize)

Bem, precisamos agora criar a página que irá apresentar as versões de nosso objeto, a página newsRevision.xhtml é apresentada abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<ui:composition xmlns="http://www.w3.org/1999/xhtml"
                xmlns:s="http://jboss.com/products/seam/taglib"
                xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:f="http://java.sun.com/jsf/core"
                xmlns:h="http://java.sun.com/jsf/html"
                xmlns:rich="http://richfaces.org/rich"
                template="layout/template.xhtml">
 
<ui:define name="body">
 
    <h:messages globalOnly="true" styleClass="message" id="globalMessages"/>
 
    <rich:panel>
        <f:facet name="header">Revision history for news id #{newsRevision.newsId}</f:facet>
			<rich:dataTable value="#{revisions}" var="_revision">
				<rich:column>
					<f:facet name="header">Title</f:facet>
					#{_revision[0].title}
				</rich:column>	
				<rich:column>
					<f:facet name="header">Contents</f:facet>
					#{_revision[0].contents}
				</rich:column>	
				<rich:column>
					<f:facet name="header">Version</f:facet>
					#{_revision[1]}
				</rich:column>
				<rich:column>
					<f:facet name="header">Modified at</f:facet>
					#{newsRevision.fetchDate(_revision[1])}
				</rich:column>
				<rich:column>
					<f:facet name="header">Action</f:facet>
					<s:link action="#{newsRevision.replaceRevision(_revision[0])}" value="Replace current instances"></s:link>
				</rich:column>	
			</rich:dataTable>
        <div style="clear:both"/>
 
    </rich:panel>
 
</ui:define>
 
</ui:composition>

Esta página precisa de receber um parâmetro chamado newsId, por isso, adicione as seguintes linhas no seu pages.xml

19
20
21
<page view-id="/newsRevision.xhtml" action="#{newsRevision.fetchRevisions}">
		<param name="id" value="#{newsRevision.newsId}"/>
	</page>

A tela produzida pelo código acima é mostrada na figura abaixo:

Listagem com as versões (clique para fullsize)

Listagem com as versões (clique para fullsize)

Finalmente a nosso backing bean associado à página (NewsRevisionService.java):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.furiousbob.beans;
 
import java.io.Serializable;
import java.util.Date;
import java.util.List;
 
import javax.persistence.EntityManager;
 
import org.jboss.envers.VersionsReader;
import org.jboss.envers.VersionsReaderFactory;
import org.jboss.envers.query.VersionsRestrictions;
import org.jboss.seam.annotations.Factory;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.datamodel.DataModel;
 
import com.furiousbob.com.News;
 
@Name("newsRevision")
public class NewsRevisionService implements Serializable {
 
	@In
	private EntityManager entityManager;
 
	private Long newsId;
 
	@DataModel(value="revisions")
	private List revisions;
 
 
	@Factory(value="revisions")
	public void fetchRevisions(){
		VersionsReader reader = VersionsReaderFactory.get(entityManager);
		this.revisions = reader.createQuery().forRevisionsOfEntity(News.class, false).add(VersionsRestrictions.idEq(newsId)).getResultList();
	}
 
	public Date fetchDate(long n){
		VersionsReader reader = VersionsReaderFactory.get(entityManager);
		Date d = reader.getRevisionDate((int)n);
		return d;
	}
 
	public void replaceRevision(News n){
		entityManager.merge(n);
	}
 
	public Long getNewsId() {
		return newsId;
	}
 
 
	public void setNewsId(Long newsId) {
		this.newsId = newsId;
	}
 
 
 
}

Para buscar as revisões de uma entidade, usamos o método fetchRevisions, para acessar o SessionFactory do envers, basta criarmos seu objeto e passarmos como referência nosso entityManager. O método createQuery().forRevisionsOfEntity(News.class, false) retorna uma lista de arrays de tamanho 2, onde a primeira posição é a entidade procurada e a segunda posição o número da revisão. Além disto precisamos buscar a data em que a revisão foi feita, note que em nosso template usamos a EL extendida do Seam para invocar um método no nosso backing bean passando um parâmetro para o mesmo #{newsRevision.fetchDate(_revision[1])}.
Nosso backing bean possui ainda um método para voltar com a versão da entidade, de acordo com a versão selecionada.
Este exemplo demonstra como é fácil gerar versionamento de entidades em seu sistema com o envers. Usando o envers e a API hibernate é possível realizar operações de restauração de objetos ou então apenas auditoria no seu sistema.
Isso é apenas um exemplo simples, uma funcionalidade desejável seria a adição do usuário corrente dentro de sua entidade, para isso basta usar as interfaces de callback do Hibernate (@PrePersist,@PreUpdate e @PreRemove) de forma que adicionem o usuário corrente em uma propriedade base de sua entidade.
Uma coisa que na minha opinião, faltou ao envers é expor de forma simples, além da versão e timestamp, o tipo de operação (INSERT,UPDATE,REMOVE) que foi realizada na entidade. Mas para isso, já abri um JIRA junto ao projeto, se você acha que esta funcionalidade vai ajudá-lo por favor vote nela para aumentar a chance de a mesma ser implementada ;)

Bem, acho que ficamos por aqui, próximos posts vou sair um pouco de Seam e vou falar de WebServices, fiquem atentos.

Inté

First things first

No último post, apresentei uma aplicação completa com Seam e jBPM. Bem, no mundo real, tarefas devem ser executas em uma determinada ordem. Algumas tarefas tem precedência em relação a outras. No jBPM isso é controlado através do atributo priority. O Seam possui suporte para apresentar as tasks para um determinado actor de acordo com sua prioridade. É o componente chamado taskInstancePriorityList. Podemos usar este componente para listar as tasks de um determinado usuario em ordem de prioridade (1=MAIOR, 5=MENOR).
Na tela onde você apresenta a task para o usuário basta usar este componente ao inves do taskInstanceList:

1
2
3
4
5
6
7
8
9
10
<rich:dataTable value="#{taskInstancePriorityList}" var="_task">
			<rich:column styleClass="d#{_task.priority}">
				<f:facet name="header">ID</f:facet>
				<h:outputText value="#{_task.id}"/>
			</rich:column>
			<rich:column>
				<f:facet name="header">Descri&ccedil;&atilde;o</f:facet>
				<s:link action="#{funcionarioService.avaliarRetorno}" taskInstance="#{_task}"><h:outputText value="#{_task.name}"/></s:link>
			</rich:column>
		</rich:dataTable>

Com o código acima suas tasks estarão agora ordenadas por prioridade, na linha 2, usei um stylesheet baseado na prioridade da task, assim, tasks com prioridade 1 teriam o fundo vermelho, 2 amarelo e 3 verde. O CSS (desculpe a simplicidade, mas eu não sou expert em user interface)

.d1{
	background-color: red;
}
.d2{
	background-color: yellow;
}
.d3{
	background-color: green;
}
 
.d4{
	background-color: green;
}
.d5{
	background-color: green;
}

Agora você garante que suas tarefas mais importantes serão vistas primeiro que as de menor prioridade. Estaria tudo ok se não fosse um pequeno problema. O TaskInstancePriorityList, ordena apenas as tasks atribuídas a um actor. E se precisarmos de uma lista de tasks para um pool de usuários ordenada por prioridade? O Seam, não possui, mas é muito fácil criar uma (o exemplo que coloquei aqui, eu gerei um JIRA no Seam para que eles pudessem incluir em futuras versões, se você gostou, vote nele para que possa ser executado).

O exemplo abaixo vai mostrar como é fácil criar um componente Seam:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package com.furiousbob.components;
 
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
 
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Install;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Transactional;
import org.jboss.seam.annotations.Unwrap;
import org.jboss.seam.bpm.Actor;
import org.jbpm.JbpmContext;
import org.jbpm.taskmgmt.exe.TaskInstance;
 
@Name("pooledTaskPriorityList")
@Install(dependencies="org.jboss.seam.bpm.jbpm",precedence=Install.BUILT_IN)
public class PooledTaskPriority implements Serializable {
	@In
	private Actor actor;
 
	@In
	private JbpmContext jbpmContext;
 
	@SuppressWarnings("unchecked")
	@Unwrap
	@Transactional
	public List<TaskInstance> getTaskPriority(){
		List<TaskInstance> tasks = jbpmContext.getTaskMgmtSession().findPooledTaskInstances(new ArrayList<String>(actor.getGroupActorIds()));
		Collections.sort(tasks, new Comparator<TaskInstance>(){
			public int compare(TaskInstance o1, TaskInstance o2) {
				return (o1.getPriority() > o2.getPriority()) ? 1 : (o1.getPriority() < o2.getPriority() ? -1 : 0);
			}
		});
		return tasks;
	}
}

Vamos lá, a linha 18 declara nosso componente, como qualquer outro, assim você pode invocar #{pooledTaskPriorityList} de qualquer página que o mesmo será invocado.
A novidade é a linha 19 com a anotação @Install. Esta anotação faz com que nosso componente seja criado no momento da inicialização do Seam. O atributo precedence garante que será instalado com a precedência mais alta, tornando sua substituição mais tarde uma tarefa fácil, e dependencies informa que poderá ser instalado apenas após os componentes de BPM do seam estarem disponibilizados (principalmente nosso jbpmContext).

Agora uma das coisas mais legais do Seam. @Unwrap “engana” o container. O que esta anotação faz é o seguinte:

  • usuário solicita uma instância de pooledTaskPriorityList
  • O Seam invoca o componente.
  • O Componente esta anotado com @Unwrap
  • Ao invés de devolver uma instância de PooledTaskPriorityList, o seam devolve um tipo definido pelo método anotado com @Unwrap.

A anotação @Unwrap lhe permite gerar componentes seam para classes que não são gerenciadas por ele. Por exemplo se você quiser injetar um EJB remoto, mas o mesmo não é gerenciado pelo Seam, basta criar uma classe com um método que faz a busca no JNDI e retorna a referência do EJB. Desta forma você pode agora usar o EJB injetando o mesmo nos seus componentes. Aqueles que não estão acostumados isso é um padrão de projeto muito antigo e muito usado em java Proxy. Cool right?

Para usar seu novo componente basta usar #{pooledTaskPriorityList} agora no seu código. A figura abaixo mostra um exemplo deste componente em ação:

Tarefas ordenadas por prioridade (clique para fullsize)

Tarefas ordenadas por prioridade (clique para fullsize)

Note que as tarefas agora não estão mais ordenadas por sua ordem natural (id), mas sim pela prioridade.
Espero que tenham gostado. Bom divertimento com Seam e jBPM, no próximo post vou falar de um outro assunto que esta relacionado a processos : SLAs.
Inté