Posts tagged ‘Add new tag’

Cluster-Safe Timer no JBoss - sem usar EJBTimers ;)

O uso de timers no jboss não é nenhum mistério, você consegue achar vários links na internet explicando como criar um EJB Timer. Entretanto, quando temos um cluster de servidores encontramos um problema: Qual instância deverá executar o serviço de timer. Este tipo de serviço é do tipo Highlander (somente um pode existir), pois caso contrário podemos ter problemas de concorrência.

O Jboss oferece duas maneiras de se disponibilizar um serviço único dentro de um cluster (e isso é bem anterior a proposição de @Singleton do EJB 3.1). A primeira, seria colocarmos nosso arquivo no diretorio HA-Singleton, mas nem sempre funciona, uma vez que algumas dependências podem ainda não terem sido resolvidas no momento da inicialização da sua aplicação. A outra maneira, é criar uma dependência com o BarrierController.

Existe um link no wiki do jboss que demonstra como criar o serviço de timer em cluster. O que vou mostrar aqui é uma alternativa usando MDBs que na minha opinião é muito mais simples que a usando Timers, você pode comparar ambas e ver a diferença.

Criando um MDB sem JMS

Message Driven Beans são amplamente utilizados em aplicações que usam batch processing de forma que possam ser executados de forma assíncrona. A sua forma mais tradicional é ouvindo uma fila JMS, mas o que muitos não sabem, é que um MDB pode ouvir outros eventos, desde que exista um Resource Adapter configurado para ele. Você pode criar um RA para um CICS, ERP ou outro sistema legado que possa notificar um MDB por exemplo.

O Jboss oferecer um RA chamado quartz-ra.rar que permite que eventos gerados pelo quartz possam notificar um MDB. Com esta abordagem teremos o nosso timer, so que ao invés de escutarmos uma fila JMS vamos escutar um Job quartz.

O código de nosso MDB se encontra na listagem abaixo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.furiousbob.timer;
 
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
 
public class ConsoleTimer implements Job {
 
	public void execute(JobExecutionContext ctx) throws JobExecutionException {
		System.out.println("WAKE UP!!!!!!");
 
	}
 
}

A primeira coisa que vocês devem ter notado é que esta classe não implementa javax.jms.MessageListener, pode soar estranho, mas como havia dito um MDB não precisa depender de uma fila JMS e como não vamos ouvir uma fila, então por que devemos implementar uma interface de JMS?

Por isso implementamos então a interface de execução de jobs do quartz.

Neste primeiro exemplo não vamos usar anotações. Isso pois acredito que a frequência de execução deveria ficar em um arquivo xml e não dentro do MDB, uma segunda listagem será apresentada mostrando a alternativa com anotações

Em termos de código é só isso :), precisamos agora configurar nosso ejb-jar.xml para informar que usamos um MDB:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8" ?>
<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.0"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd">
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<ejb-class>com.furiousbob.timer.ConsoleTimer</ejb-class>
		<messaging-type>org.quartz.Job</messaging-type>
		<transaction-type>Container</transaction-type>
		<activation-config>
			<activation-config-property>
				<activation-config-property-name>cronTrigger</activation-config-property-name>
				<activation-config-property-value><![CDATA[30 * * * * ?]]></activation-config-property-value>				
			</activation-config-property>
		</activation-config>
	</message-driven>
</enterprise-beans>
</ejb-jar>

O que temos de diferente neste arquivo são as linhas 10 e 12-17. Na linha 10 informamos ao container que o tipo de interface que vamos usar, em anotações teríamos algo como @MessageDriven(messageListenerInterface=Job.class). As linhas 12-17 são configurações do bean, onde normalmente informaríamos o nome da queue/topic a ser consumida, aqui informamos a expressão do cronTrigger (para maiores informações consulte este link)

Agora precismos informar ao JBoss qual resource adapter vamos usar para este bean, para isso modifique o arquivo jboss.xml:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<resource-adapter-name>quartz-ra.rar</resource-adapter-name>
	</message-driven>
</enterprise-beans>
</jboss>

Pronto, agora basta fazer deploy de seu novo MDB que é na verdade um timer. Você irá notar o console tendo saídas a cada minuto sempre aos 30s de cada minuto.

Cluster Safe Timers

Ok, agora você tem seu timer, ele acorda a cada X segundos, mas você precisa de um cluster de alta disponibilidade, ou seja você precisa que o serviço esteja sempre disponível, mesmo quando uma máquina falhe. A primeira idéia seria colocar este serviço replicado em cada nó do cluster, como fazemos com um SLSB ou SFSB. O problema dessa abordagem é que no caso de SLSB como não possuem estado tanto faz onde o cliente “aterrisa”, no caso de SFSB o JBoss (e sinceramente, o mecanismo de cluster mais fantástico de todos App Servers que conheço), replica o estado entre os nós usando JBossCache/JGroups para você. Agora e no caso de um serviço que pode existir apenas uma instância em execução? Neste caso, adicionamos ao nosso serviço uma dependência com o servico BarrierController como o arquivo jboss.xml modificado abaixo:

1
2
3
4
5
6
7
8
9
10
<?xml version="1.0" encoding="UTF-8"?>
<jboss>
<enterprise-beans>
	<message-driven>
		<ejb-name>ConsoleTimer</ejb-name>
		<resource-adapter-name>quartz-ra.rar</resource-adapter-name>
		<depends>jboss.ha:service=HASingletonDeployer,type=Barrier</depends>  
	</message-driven>
</enterprise-beans>
</jboss>

Note que agora temos uma mudança na linha 7 indicando a dependência com o serviço. Pronto, seu serviço esta pronto para rodar em clusters de forma consistente. Sempre que um nó cair, outro toma seu lugar e faz o deploy do serviço.

Esta solução é mais simples que a apresentada pelo wiki do jboss, e já usei em produção em sistemas e garanto que funciona ;)

Tirando onda com seus amigos

O que vou mostrar aqui é um pequeno passo a passo de como subir duas instâncias do jboss em cluster, na mesma máquina, com replicação de estado e com o serviço de timer, isto aqui é só para você mostrar aos seus amigos como é simples criar clusters com jboss :)

Vamos usar o jboss 4.2.3.GA rodando em um Ubuntu 8.10. O primeiro passo é você fazer uma cópia da pasta all pois teremos 2 configurações distintas, eu normalmente copio a pasta all para node1 e node2:

vinicius@cybertron:~/java/jboss-4.2.3.GA/server$ cp -R all/ node1
vinicius@cybertron:~/java/jboss-4.2.3.GA/server$ cp -R all/ node2

Bem, vamos subir 2 instâncias de jboss na mesma máquina, aqui você tem 2 alternativas:

  • Subir cada instância em IPs distintos, mas mantendo as portas
  • Alterar as portas de uma instância e subir ambas na mesma interface


  • Vamos adotar a segunda abordagem. E vamos fazer o seguinte, node1 irá subir nas portas padrão (8080,1099,8009) e node2 vai subir em portas alternativas (8180,1199,8109). Se você não sabe como fazer, vai aqui mais uma dica

    Alterando as portas do JBossAS



    Como vamos alterar as portas do node2, entre em node2/conf e edite o arquivo jboss-service.xml. Procure pelo trecho mostrado abaixo:

    1
    2
    3
    4
    5
    6
    7
    8
    
     <mbean code="org.jboss.services.binding.ServiceBindingManager"
         name="jboss.system:service=ServiceBindingManager">
         <attribute name="ServerName">ports-01</attribute>
         <attribute name="StoreURL">${jboss.home.url}/docs/examples/binding-manager/sample-bindings.xml</attribute>
         <attribute name="StoreFactoryClassName">
           org.jboss.services.binding.XMLServicesStoreFactory
         </attribute>
       </mbean>

    O trecho apresentado estará comentado, basta remover o comentário do mesmo. O que isto faz é ler um arquivo de bindings (dê uma lida no arquivo para entender sua estrutura) e então atribuir as portas de serviço do jboss de forma dinâmica. Note que no arquivo existem várias pre-configurações (ports-default, ports-01,ports-02….) de forma a simplificar sua vida :)

    Bem, agora quando você subir a instância node2 ela não terá conflito de portas com node1.

    Cluster e Multicast



    Toda comunicação entre os nós do cluster no jboss é feita por multicasting, portanto você precisa habilitar uma rota de multicast em seu linux (no windows isso não é necessário). Eu recomendo que vocês dêem uma lida neste artigo, mas para simplificar as coisas vou passar o comando que devem executar em seu linux ;)


    route add -net 224.0.0.0 netmask 240.0.0.0 dev eth0

    O -net é necessário apenas em algumas distro (no ubuntu é necessário)

    Pronto, agora você esta pronto para executar suas instâncias de jboss. Abra duas janelas de terminal e execute os comando abaixo no diretório bin/

    vinicius@cybertron:~/java/jboss-4.2.3.GA/bin$ ./run.sh -c node1
    vinicius@cybertron:~/java/jboss-4.2.3.GA/bin$ ./run.sh -c node2
    

    Para verificar se suas instâncias estão se comunicando procure pela mensagem abaixo em um dos consoles

    -------------------------------------------------------
    GMS: address is 127.0.0.1:42880
    -------------------------------------------------------
    10:55:32,391 INFO  [DefaultPartition] Number of cluster members: 2
    10:55:32,391 INFO  [DefaultPartition] Other members: 1
    10:55:32,391 INFO  [DefaultPartition] Fetching state (will wait for 30000 milliseconds):
    10:55:32,504 INFO  [DefaultPartition] state was retrieved successfully (in 112 milliseconds)
    10:55:32,622 INFO  [HANamingService] Started ha-jndi bootstrap jnpPort=1200, backlog=50, bindAddress=/127.0.0.1
    10:55:32,627 INFO  [DetachedHANamingService$AutomaticDiscovery] Listening on /127.0.0.1:1102, group=230.0.0.4, HA-JNDI address=127.0.0.1:1200
    10:55:32,877 INFO  [TreeCache] No transaction manager lookup class has been defined. Transactions cannot be used
    10:55:33,235 INFO  [STDOUT]
    -------------------------------------------------------
    GMS: address is 127.0.0.1:56144
    -------------------------------------------------------
    

    Note a existência de 2 clusters :)

    Agora pegue seu arquivo modificado com a dependência do BarrierController e faça deploy em uma instância, depois realize o procedimento na outra instância. Note que em apenas um dos consoles o timer será executado. Agora, mate o processo no console que esta rodando o serviço, note que logo após ter matado o serviço o outro console irá assumir controle e registrará a seguinte mensagem:

    10:59:27,627 INFO  [EJBContainer] STARTED EJB: com.furiousbob.timer.ConsoleTimer ejbName: ConsoleTimer
    

    A partir daí o novo nó será responsável pela execução do servico. Muito legal né? Espero que tenham curtido o post.

    Grande Abraço

    Uma aplicação completa com Seam e jBPM

    Recentemente, ministrei um treinamento de JBoss jBPM e JBoss Seam pela Red Hat para o pessoal da Justiça Federal de Santa Catarina. Durante o treinamento, resolvermos pular os exemplos do material oficial e então decidimos mapear um processo sugerido pela turma.

    Como a ideia foi bacana, resolvi blogar com um exemplo completo (infelizmente no treinamento não tivemos tempo hábil para terminar a implementação).

    Nosso processo é uma versão simples de um processo de solicitação de horas extras. Neste processo temos dois atores bem distintos o Funcionário, que solicita hora extra para a execução em um determinado projeto, e um gerente que aprova ou não esta solicitação.

    O processo de exemplo esta representado pela figura abaixo. É póssivel identificar que existem 2 tarefas humanas. Uma associada à todos usuários cujo perfil seja de gerentes (Verificação de Solicitações) e outra associada ao usuário que iniciou esta solicitação (Validação da Solicitação)

    O processo funciona da seguinte maneira:

    1. O funcionário solicita uma determinada quantidade de horas para realizar a mais em um projeto
    2. O gerente aprova ou não estas horas
    3. Caso o gerente aprove, o funcionário decide aceitar as horas, ou então no caso de uma aprovação parcial, re-submeter sua solicitação para re-avaliação do gerente 1

    1 No mundo fantástico de Bobby, Gerentes são seres com capacidade de aceitar críticas e sugestões de seus funcionários.

    Exemplo de processo de aprovação de horas (clique para fullsize)

    Exemplo de processo de aprovação de horas (clique para fullsize)

    O código abaixo demonstra o processo mapeado no jBPM. Note a presença de duas tasks. Uma task [linhas 19-28] esta associada a qualquer usuário cujo papel seja “manager”, no jBPM um pooled-actor é uma forma de representar um Role (swimlanes também servem para isso, mas ficaram de fora deste meu primeiro post). A segunda task [linhas 11-17] é a task em que o usuário deverá definir se aceita a aprovação do gerente (acho que isso pode ser considerado um exemplo de inversão de controle não acham?), ou solicita uma revisão. Notem que para esta task usamos uma classe para realizar o Assigment, a classe que realizar esta tarefa esta representada na listagem 2.

    Note que na linha 22, usamos uma expressão #{identity.username} para representar a descrição da tarefa. Isto é apenas para facilitar a leitura da lista de tarefas de um gerente. Como o objeto identity vai parar ai dentro e o que ele representa será explicado mais 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
    
    <?xml version="1.0" encoding="UTF-8"?>
     
    <process-definition 
      xmlns="urn:jbpm.org:jpdl-3.2"
      name="AprovacaoHoras">
     
     
    	<start-state name="start">
    		<transition to="Verificacao de Solicitacoes"></transition>
    	</start-state>
       <task-node name="Validacao da Solicitacao">
    		<task name="Validar Solicitacao">
    			<assignment class="com.furiousbob.action.ActorAssignmentHandler"></assignment>
    		</task>
    		<transition to="Verificacao de Solicitacoes" name="Solicitar Reavaliacao"></transition>
    		<transition to="Aprovado" name="Aceitar"></transition>
    	</task-node>
     
    	<task-node name="Verificacao de Solicitacoes">
    		<task name="Verificar Solicitacao">
    			<description>
    				Solicitacao de Hora extra funcionario #{identity.username}
    			</description>
    			<assignment pooled-actors="manager"></assignment>
    		</task>
    		<transition to="Reprovado" name="Reprovar"></transition>
    		<transition to="Validacao da Solicitacao" name="Aprovar"></transition>
    	</task-node>
     
     
    	<end-state name="Reprovado"></end-state>
     
    	<end-state name="Aprovado"></end-state>
    </process-definition>
    public class ActorAssignmentHandler implements AssignmentHandler {
     
    	public void assign(Assignable assignable, ExecutionContext executionContext)
    			throws Exception {
    		assignable.setActorId((String) executionContext.getVariable("initiator"));
    	}

    Listagem 2 - Exemplo de um AssignmentHandler

    Como havia mencionado anteriormente, vamos aproveitar o jbpm-console como base para nossa aplicação (isso inclui o mecanismo de identity dele). Por isso, para este exemplo funcionar você precisa criar 3 usuários e associá-los a perfis dentro do jbpm-console. Basta logar-se como admin no console e ir na aba Identities.
    Os usuários e seus grupos são:

    • asterix:user
    • obelix:user
    • chatotorix:manager 2

    2 - Qualquer semelhança entre o nome do usuário e alguma pessoa do mundo real é mera coincidência

    Isso conclui toda parte do jBPM. Vamos agora para a configuração necessária para rodar este exemplo. Para facilitar as coisas, decidi usar o jboss que vem junto com o jBPM. Desta forma, vamos usar o web-console do jBPM para acompanhar o processo e para realizar o deploy, e o Seam para gerar nossa aplicação. Baixe um versão do jPDL suite.
    Uma vez que você estiver com o servidor instalado. Baixe a última versão do JBoss Tools. Agora você pode criar um novo projeto jBPM, adicionar a classe e process definition apresentados aqui e então realizar o deploy. Ao logar no jbpm-console você deve ter algo assim:

    Console do jBPM (clique para fullsize)

    Console do jBPM (clique para fullsize)

    Agora você precisar criar o projeto Seam, a maneira mais fácil de fazer isso é baixando o Seam (usei a versão 2.0.2.SP1), e usar o seam-gen para gerar seu projeto, mais informações podem ser encontradas no reference do projeto.

    Responda a todas perguntas do seam-gen, use o HSQL como banco default e no momento em que ele lhe pedir a url de conexão use:

    jdbc:hsqldb:${jboss.server.data.dir}${/}hypersonic${/}jbpmDB

    Isso vai garantir que nossa aplicação vai se conectar no mesmo banco de dados do jBPM console. Após gerar o projeto, você precisa agora adicionar a lib de Identities do jBPM ao seu projeto. Para isso copie o arquivo jbpm-identity.jar do jbpd-suite para o seu diretorio de lib.
    Edite o arquivo build.xml do seu projeto, e altere a propriedade deploy.dir para ficar como abaixo, caso contrário o seam tentará fazer deploy na instância default, que simplesmente não existe no jpdl-suite server ;)

    
    

    Agora você precisa garantir que o arquivo jbpm-identity.jar seja copiado para seu ear. No seam-gen você faz isto ediando o arquivo deployed-jars-ear.list. Adicione a linha jbpm-identity.jar.

    Estamos quase lá, agora é preciso adicionar suporte a jBPM dentro da sua aplicação seam. Faça isto editanto o arquivo components.xml e adicione as linhas abaixo:

     <bpm:jbpm>
          <bpm:process-definitions></bpm:process-definitions>
       </bpm:jbpm>

    Agora você precisa integrar o mecanismo de persistência do jBPM ao Seam. Primeiro adicione o arquivo jbpm.cfg.xml na pasta resources do seu projeto. O conteúdo do arquivo esta na listagem abaixo

    jbm.cfg.xml

    <jbpm-configuration>
     
    <jbpm-context>
      <service name="persistence">
        <factory>
          <bean
            class="org.jbpm.persistence.db.DbPersistenceServiceFactory">
            <field name="isTransactionEnabled"><false/></field>
           </bean>
         </factory>
      </service>
      <service name="tx"
        factory="org.jbpm.tx.TxServiceFactory"/>
      <service name="message"
        factory="org.jbpm.msg.db.DbMessageServiceFactory"/>
        <service name="scheduler"  factory="org.jbpm.scheduler.db.DbSchedulerServiceFactory"/>
    <service name="logging"
      factory="org.jbpm.logging.db.DbLoggingServiceFactory"/>
        <service name="authentication"
      factory="org.jbpm.security.authentication.DefaultAuthenticationServiceFactory"/>
     
    </jbpm-context>
     
     
    </jbpm-configuration>

    Basicamente, o que este arquivo faz é informar ao jbpmContext que será encapsulado pelo Seam, que não use seu mecanismo de transação, assim o Seam será responsável por gerenciar transações e garantir atomicidade das operações (por exemplo um processo não será criado caso um método de um session bean que o cria lançar uma exceção pertinente ao seu negócio)

    Agora precisamos adicionar o hibernate.cfg.xml, este arquivo é muito grande, e não vou postar ele aqui, você pode pegar o que se encontra em WEB-INF/classes do jbpm-console.war e copiá-lo para seu diretório de resources. Precisa alterar apenas as linhas que vou delimitar abaixo:

    23
    
    <property name="hibernate.connection.datasource">java:JbpmDS</property>

    Descomente o mapeamento do componente identity

    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    
      <!-- following mapping files have a dependency on  -->
        <!-- 'jbpm-identity.jar', mapping files            -->
        <!-- of the pluggable jbpm identity component.     -->
        <!-- Uncomment the following 3 lines if you        -->
        <!-- want to use the jBPM identity mgmgt           -->
        <!-- component.                                    -->
        <!-- identity mappings (begin) ===
        <mapping resource="org/jbpm/identity/User.hbm.xml"/>
        <mapping resource="org/jbpm/identity/Group.hbm.xml"/>
        <mapping resource="org/jbpm/identity/Membership.hbm.xml"/>
        -->

    Edite seu arquivo persistence.xml para ficar semelhante ao abaixo (principalmente as linhas 14 e 17)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    <?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="AprovacaoHoras">
          <provider>org.hibernate.ejb.HibernatePersistence</provider>
          <jta-data-source>java:/AprovacaoHorasDatasource</jta-data-source>
          <properties>
             <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
             <property name="hibernate.show_sql" value="true"/>
             <property name="hibernate.hbm2ddl.auto" value="update"/>
             <property name="hibernate.format_sql" value="true"/>
             <property name="jboss.entity.manager.factory.jndi.name" value="java:/AprovacaoHorasEntityManagerFactory"/>
             <property name="hibernate.ejb.cfgfile" value="/hibernate.cfg.xml"></property> 
          </properties>
       </persistence-unit>
     
    </persistence>

    Nota: No exemplo que estamos criando aqui, usamos a mesma base de dados para o acesso do jBPM e sua aplicação, isto nem sempre será o desejado uma vez que o jBPM cria uma quantidade considerável de tabelas que podem se misturar no seu domínio. O certo então, seria você utilizar dois datasources e definir os mesmos como recursos XA, habilitando assim o processo de two-phase commit. Para maiores informações acesse o wiki do JBoss.

    Poxa, finalmente! Terminamos com a parte de configuração, agora vem a parte divertida :)

    O mecanismo de autenticação do Seam (um tópico para um post posterior), permite que utilizemos uma classe qualquer para realizar a autenticação. A única restrição, é que esta classe tenha um método que retorne um booleano e não possua parâmetros. A listagem abaixo mostra a nossa implementação (que vai buscar os usuários do banco de dados do jbpm console que foram criados mais cedo)

    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
    
    package com.furiousbob.beans;
     
    import java.util.Iterator;
     
    import javax.persistence.EntityManager;
    import javax.persistence.Query;
     
    import org.jboss.seam.annotations.In;
    import org.jboss.seam.annotations.Logger;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.bpm.Actor;
    import org.jboss.seam.log.Log;
    import org.jboss.seam.security.Identity;
    import org.jbpm.identity.Membership;
    import org.jbpm.identity.User;
     
     
    @Name("authenticator")
    public class Authenticator
    {
        @Logger Log log;
     
        @In Identity identity;
     
        @In
        private EntityManager entityManager;
     
        @In
        private Actor actor;
     
        public boolean authenticate()
        {
        	Query query = entityManager.createQuery("from " + User.class.getName() +
                                                    "  u where u.name = :name and u.password = :password");
        	query.setParameter("name", identity.getUsername());
        	query.setParameter("password", identity.getPassword());
            User user = (User) query.getSingleResult();
            if(user == null){
            	return false;
            }else{
            	actor.setId(user.getName());
            	Iterator memberships = user.getMemberships().iterator();
            	while(memberships.hasNext()){
            		Membership m = memberships.next();
            		actor.getGroupActorIds().add(m.getGroup().getName());
            		identity.addRole(m.getGroup().getName());
            	}
            }
        	return true;
        }
    }

    A grande sacada aqui é o componente Actor que estamos populando com os papéis que o usuário possui. O Seam irá injetar este componente no nosso processo jBPM e també irá utilizá-lo quando necessitarmos de buscar alguma task para um determinado usuário ou grupo de usuários (actor-id e pooled-actor-ids respectivamente).

    Disponibilize a aplicação (task explode do ant), e agora sua aplicação já possui uma tela de login que funciona de verdade, experimente :)

    Vamos criar agora uma entidade chamada SolicitacaoHoraExtra que vai armezar as solicitações do usuário. A classe esta representada 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
    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
    
    package com.furiousbob.model;
     
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.SequenceGenerator;
    import javax.persistence.Table;
     
    import org.jboss.seam.annotations.Name;
     
    @Name("solicitacao")
    @Entity
    @Table(name="SOLICITACAO_HORA_EXTRA")
    @SequenceGenerator(name="SOLICITACAO_GENERATOR",sequenceName="SEQ_HORAS_EXTRAS")
    public class SolicitacaoHoraExtra {
     
    	@Id
    	@GeneratedValue(generator="SOLICITACAO_GENERATOR",strategy=GenerationType.SEQUENCE)
    	private Long id;
     
    	private String projeto;
     
    	private String funcionario;
     
    	private Integer qtdHoras;
     
    	private Integer qtdAprovadas;
     
    	private String observacoes;
     
    	public Long getId() {
    		return id;
    	}
     
    	public String getProjeto() {
    		return projeto;
    	}
     
    	public String getFuncionario() {
    		return funcionario;
    	}
     
    	public Integer getQtdHoras() {
    		return qtdHoras;
    	}
     
    	public Integer getQtdAprovadas() {
    		return qtdAprovadas;
    	}
     
    	public void setId(Long id) {
    		this.id = id;
    	}
     
    	public void setProjeto(String projeto) {
    		this.projeto = projeto;
    	}
     
    	public void setFuncionario(String funcionario) {
    		this.funcionario = funcionario;
    	}
     
    	public void setQtdHoras(Integer qtdHoras) {
    		this.qtdHoras = qtdHoras;
    	}
     
    	public void setQtdAprovadas(Integer qtdAprovadas) {
    		this.qtdAprovadas = qtdAprovadas;
    	}
     
    	public String getObservacoes() {
    		return observacoes;
    	}
     
    	public void setObservacoes(String observacoes) {
    		this.observacoes = observacoes;
    	}
    }

    O gerador de IDs desta classe é baseado em sequences, você precisa criar a sequence no HSLQDB, para isso, acesse o jmx-console -> database=jbpmDB,service=Hypersonic -> startDatabaseManager()

    Esta operação do jmx-console vai iniciar o aplicativo swing que você pode usar para se conectar ao banco de dados do jbpm. Execute o script abaixo para criar a sequence

    CREATE TABLE DUAL_SEQ_HORAS_EXTRA (ZERO INTEGER);
    CREATE SEQUENCE SEQ_HORAS_EXTRAS START WITH 1;
    INSERT INTO DUAL_SEQ_HORAS VALUES(1);

    Bem, agora precisamos decidir como vamos iniciar nosso processo. O gatilho para a criação de uma nova instância é o momento em que um funcionário decide solicitar hora extra, então vamos criar um componente chamado funcionarioService que durante o método solicitarHoraExtra crie uma instância do processo para nós. O componente esta representado na listagem 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
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    
    package com.furiousbob.beans;
     
    import javax.persistence.EntityManager;
     
    import org.jboss.seam.ScopeType;
    import org.jboss.seam.annotations.End;
    import org.jboss.seam.annotations.In;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.annotations.Out;
    import org.jboss.seam.annotations.bpm.CreateProcess;
    import org.jboss.seam.annotations.bpm.EndTask;
    import org.jboss.seam.annotations.bpm.StartTask;
    import org.jboss.seam.security.Identity;
     
    import com.furiousbob.model.SolicitacaoHoraExtra;
     
    @Name("funcionarioService")
    public class FuncionarioService {
     
     
     
    	@Out(required=false,scope=ScopeType.BUSINESS_PROCESS)
    	private String initiator;
     
    	@In
    	private EntityManager entityManager;
     
    	@In
    	private Identity identity;
     
    	@In(create=true)
    	@Out(required=false)
    	private SolicitacaoHoraExtra solicitacao;
     
    	@Out(scope=ScopeType.BUSINESS_PROCESS)
    	@In(required=false)
    	private Long idSolicitacao;
     
    	@CreateProcess(definition="AprovacaoHoras")
    	@End
    	public void solicitarHoraExtra(){
    		solicitacao.setFuncionario(identity.getUsername());
    		entityManager.persist(solicitacao);
    		idSolicitacao = solicitacao.getId();
    		initiator = identity.getUsername();
    	}
     
    	@StartTask()
    	public void avaliarRetorno(){
    		solicitacao = entityManager.find(SolicitacaoHoraExtra.class, idSolicitacao);
    	}
     
    	@EndTask(transition="Aceitar")
    	public void concordar(){
    		initiator = identity.getUsername();
    	}
     
    	@EndTask(transition="Solicitar Reavaliacao")
    	public void resubmeter(){
    		initiator = identity.getUsername();
    		entityManager.merge(solicitacao);
    	}
     
    }

    O grande momento na interação Seam jBPM ocorre entre as linhas 37 e 44. Quando este método é executado o Seam irá criar a instância do processo de nome “AprovacaoHoras”, se você não acredita, vá até seu jbpm-console e veja que existe uma instância do processo em execução:

    Instância do seu processo (clique para fullsize)

    Instância do seu processo (clique para fullsize)

    Agora você deve estar se perguntando, o que aqueles @Out estão fazendo para você? Quando seu método é executado, e você atribui as variáveis idSolicitacao e initiator a um valor qualquer, o Seam usa seu mecanismo de ejeção (Outjection), e coloca estes atributos no escopo do processo, com isso, estas variáveis serão armazenadas no processo (usem o link “Show process variables” para verem as variáveis exportadas).
    A grande pergunta é: o que armazenar? Pense assim, você armazena tudo aquilo que um outro usuário ou evento dentro do seu processo pode vir a precisar em um certo momento que não esteja compreendido entre a execução de seu método. Claro que você poderia armazenar um objeto completo como o SolicitacaoHoraExtra, mas para que isso, se você pode armazenar apenas seu ID e posteriormente recuperar o objeto (que poderia inclusive ter sofrido alguma alteração no banco caso você o tivesse armazenado completamente) e utilizá-lo, e é exatamente isso que faremos em breve.

    O template xhtml que acessa esta tela é mostrado na listagem 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
    
    <!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"/>
     
        <rich:panel>
        <f:facet name="header">Formul&aacute;rio de solicita&ccedil;&atilde;o de Hora Extra</f:facet>
    		<h:form>
    		<table>
    			<tr>
    				<td>Projeto: </td>
    				<td><h:inputText value="#{solicitacao.projeto}"/> </td>
    			</tr>
    			<tr>
    				<td>Horas Solicitadas</td>
    				<td><h:inputText value="#{solicitacao.qtdHoras}"/> </td>
    			</tr>
    			<tr>
    				<td colspan="2">
    					<h:commandButton value="Solicitar" action="#{funcionarioService.solicitarHoraExtra}"/>
    				</td>
    			</tr>
    		</table>
    		</h:form>
        </rich:panel>
     
    </ui:define> 
    </ui:composition>

    Bem, isso termina a etapa de criação do processo, temos agora que desenvolver uma forma onde os gerentes possam examinar e então aprovar ou não as solicitações de seus funcionários.

    Para que os gerentes possam ver as tarefas que estão agendadas para eles o Seam oferece um componente que lista todas as tarefas para um determinado grupo de atores. Na verdade o Seam oferece quatro componentes que nos auxiliam nesta tarefa são eles:

    • taskInstanceList: Lista todas as tasks atribuídas a um determinado ator
    • taskInstancePriorityList: Lista todas as tasks atribuídas a um determinado ator ordenadas por prioridade
    • taskInstanceListForType: Lista todas as tasks com um determinado nome
    • pooledTaskInstanceList: Lista todas as tasks atribuídas a um determinado grupo de atores (pooled-actors)

    Vocês se lembram que quando um funcionário realizava a solicitação uma instância do processo era criada certo? Se vocês acessarem a aba “Tasks” do console jBPM poderão notar que uma tarefa foi criada, esta atribuída ao grupo “manager” mas ainda não foi iniciada. A figura abaixo mostra esta situação.

    Lista de tarefas para um determinado grupo (clique para fullsize)

    Lista de tarefas para um determinado grupo (clique para fullsize)

    Precisamos então de listar as tarefas para todos usuários do grupo “manager”. O template xhtml abaixo mostra como podemos fazer isto

    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
    
    <!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"/>
     
        <rich:panel>
        <f:facet name="header">Formul&aacute;rio de solicita&ccedil;&atilde;o de Hora Extra</f:facet>
    		<h:dataTable value="#{pooledTaskInstanceList}" var="_task">
    			<h:column>
    				<f:facet name="header">ID</f:facet>
    				<h:outputText value="#{_task.id}"/>
    			</h:column>
    			<h:column>
    				<f:facet name="header">Descri&ccedil;&atilde;o</f:facet>
    				<s:link action="#{gerenteService.avaliarSolicitacao}" taskInstance="#{_task}"><h:outputText value="#{_task.description}"/></s:link>
    			</h:column>
    		</h:dataTable>
        </rich:panel>
     
    </ui:define> 
    </ui:composition>

    Na linha 17 usamos o componente pooledTaskList do Seam, que vai buscar as tasks do usuário corrente, como ele faz isso? Lembra que no nosso Authenticator povoamos um objeto Actor? Bem, este objeto é usado pelo Seam para buscar as tasks de um Actor ou um Pooled-Actor.

    Notem que a listagem da task é realizada pela sua descrição, e que esta possui uma referência ao nome do funcionário que a solicitou. Isso foi possível pois usamos no nosso processo uma expressão EL #{identity.username}, quando o processo jBPM é criado por um evento dentro do ciclo de vida do Seam (ou seja o jBPMContext esta contido no SeamContext) o EL Resolver do jBPM é substituido por um mais amplo que faz resolução de objetos no contexto do Seam, assim, qualquer objeto que for acessível pelo contexto do Seam, será visível pelo processo naquele exato momento. Isto quer dizer que caso você tivesse um objeto com ciclo de vida “EVENT” por exemplo, no momento da criação da task você poderia usar este objeto. Lembre-se que este objetos possuem um ciclo de vida bem inferior ou do processo de negócio e podem ser acessados apenas durante o contexto do Seam, se você precisa que o objeto seja acessado posteriormente em outro evento ou por outro usuário, você precisa armazenar o objeto no processo, assim como fizemos com o idSolicitacao e initiator.

    A figura abaixo apresenta a listagem de tasks pendentes para um determinado gerente.

    Tasks associadas a um determinado grupo (clique para fullsize)

    Tasks associadas a um determinado grupo (clique para fullsize)

    Quando o gerente clicar em uma task, o método avaliarSolicitacao do bean GerenteService será executado, e a tarefa será iniciada e atribuída ao usuário corrente. A classe GerenteService é apresentada logo 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
    47
    48
    49
    50
    51
    52
    53
    
    package com.furiousbob.beans;
     
    import javax.persistence.EntityManager;
     
    import org.jboss.seam.ScopeType;
    import org.jboss.seam.annotations.End;
    import org.jboss.seam.annotations.In;
    import org.jboss.seam.annotations.Name;
    import org.jboss.seam.annotations.Out;
    import org.jboss.seam.annotations.Scope;
    import org.jboss.seam.annotations.bpm.EndTask;
    import org.jboss.seam.annotations.bpm.StartTask;
     
    import com.furiousbob.model.SolicitacaoHoraExtra;
     
    @Name("gerenteService")
    @Scope(ScopeType.CONVERSATION)
    public class GerenteService {
     
    	@Out(required=false,scope=ScopeType.BUSINESS_PROCESS)
    	private String resultado;
     
    	@In(scope=ScopeType.BUSINESS_PROCESS)
    	private Long idSolicitacao;
     
    	@In
    	private EntityManager entityManager;
     
    	@In(required=false)
    	@Out(required=false)
    	private SolicitacaoHoraExtra solicitacao;
     
    	@In
    	private String initiator;
     
    	@StartTask()
    	public void avaliarSolicitacao(){
    		solicitacao = entityManager.find(SolicitacaoHoraExtra.class, idSolicitacao);
    	}
     
    	@EndTask(transition="Reprovar")
    	@End
    	public void reprovar(){
     
    	}
     
    	@EndTask(transition="Aprovar")
    	@End
    	public void aprovar(){
    		entityManager.merge(solicitacao);
    	}
     
    }

    A parte mágica aqui são as linhas 36-39. Neste momento nossa task será iniciada. Agora vem a pergunta: Se estou com outro usuário, em outra tela, em outro bean como o Seam sabe qual processo ele vai referenciar? A resposta para isso é um atributo da tag s. Se você voltar na listagem xhtml da tela do gerente pode notar o seguinte trecho na linha 24

    
    

    Quando o gerente clica na task, antes de o método ser invocado, a task é colocada dentro do escopo conversacional (um post futuro vou explicar detalhadamente cada um dos escopos) e o seu processo pai e todas variáveis ficam agora disponíveis para todos métodos que são executados dentro desta conversação. O TREM DOIDO SÔ!!!!

    Como temos acesso a todas variáveis do processo, podemos agora injetar o ID da solicitação do usuário através do comando

    @In(scope=ScopeType.BUSINESS_PROCESS)
    	private Long idSolicitacao;

    Podemos agora recuperar a solicitação do usuário e ejetar a mesma para que fique disponível na nossa próxima tela.

    Aprovação de solicitações (clique para fullsize)

    Aprovação de solicitações (clique para fullsize)

    O template desta próxima tela esta representado na listagem 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
    47
    48
    49
    
    <!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"/>
     
        <rich:panel>
        <f:facet name="header">Formul&aacute;rio de solicita&ccedil;&atilde;o de Hora Extra</f:facet>
    		<h:form>
    		<table>
    			<tr>
    				<td>Projeto: </td>
    				<td><h:outputText  value="#{solicitacao.projeto}"/> </td>
    			</tr>
    			<tr>
    				<td>Horas Solicitadas</td>
    				<td><h:outputText value="#{solicitacao.qtdHoras}"/> </td>
    			</tr>
    			<tr>
    				<td>Horas Aprovadas</td>
    				<td><h:outputText value="#{solicitacao.qtdAprovadas}"/> </td>
    			</tr>
    			<s:fragment rendered="#{solicitacao.observacoes != null}">
    			<tr>
    				<td>Observacoes</td>
    				<td><h:inputTextarea value="#{solicitacao.observacoes}" readonly="readonly"></h:inputTextarea> </td>
    			</tr>
     
    			</s:fragment>
    			<tr>
    				<td colspan="2">
    					<h:commandButton action="#{gerenteService.aprovar}" value="Aprovar"/>
    					<h:commandButton action="#{gerenteService.reprovar}" value="Reprovar"/>
    				</td>
    			</tr>
    		</table>
    		</h:form>
        </rich:panel>
     
    </ui:define> 
    </ui:composition>

    Quando o gerente clicou na tarefa, a mesma foi automaticamente atribuída a ele, e foi iniciada pelo jBPM, para verificar isso, volte na tela de “Tasks” do jbpm-console e veja as mudanças:

    Tela do jbpm mostrando alterações da task (clique para fullsize)

    Tela do jbpm mostrando alterações da task (clique para fullsize)

    Resta agora ao nosso gerente tomar a decisão que melhor lhe convir. Existem dois botões na tela, um para aprovar e outro para reprovar. Estes botões irão invocar um dos métodos da classe GerenteService, e ambos estão anotados com @EndTask. Esta anotação vai terminar a task associada ao nosso usuário e que esta no contexto conversacional do Seam. Como no nosso processo temos duas transições, a anotação deve possuir o nome da transição a ser tomada.

    Caso o gerente decida reprovar, o processo será cancelado, pois a transição reprovar leva a um end-state. Caso contrário, será gerada agora uma tarefa que desta vez será associada ao usuário que fez a solicitação. Vamos tomar esta abordagem.

    Voltemos agora ao nosso funcionário Asterix. Sua solicitação foi aprovada, mas ele precisa saber disto. Da mesma forma que listamos as tarefas de um grupo “manager” para nosso gerente, vamos listar agora as tarefas, mas desta vez, tarefas que estejam mapeadas para o usuário específico Asterix. Dê um pulo no jbpm-console e você poderá ver que existe uma tarefa aguardando para ser iniciada e que pertence ao asterix. Esta tarefa foi criada quando a transição “Aprovar” foi tomada pelo nosso processo.

    Tarefas associadas ao usuário asterix (clique para fullsize)

    Tarefas associadas ao usuário asterix (clique para fullsize)

    A tela do Seam correspondente e o código necessário para a mesma, estão representados 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
    
    <!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"/>
     
        <rich:panel>
        <f:facet name="header">Horas Aprovadas</f:facet>
    		<h:dataTable value="#{taskInstanceList}" var="_task" border="1">
    			<h:column>
    				<f:facet name="header">ID</f:facet>
    				<h:outputText value="#{_task.id}"/>
    			</h:column>
    			<h: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>
    			</h:column>
    		</h:dataTable>
        </rich:panel>
     
    </ui:define> 
    </ui:composition>

    Notem que desta vez, usamos o componente taskInstanceList, este componente lista as task do usuário especifico e não de seu grupo. Ao entrarmos na tela notamos que nosso “amigo” chatotorix resolveu não aprovar todas as horas, a tela agora apresenta ao funcionário a opção de aceitar a decisão do gerente e terminar o processo, ou resubmeter a tarefa para a lista de tarefas do gerente, desta vez com uma observação justificando a necessidade de se usar todas as horas solicitadas. Novamente o código e a tela estão representados logo 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
    47
    
    <!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"/>
     
        <rich:panel>
        <f:facet name="header">Formul&aacute;rio de solicita&ccedil;&atilde;o de Hora Extra</f:facet>
    		<h:form>
    		<table>
    			<tr>
    				<td>Projeto: </td>
    				<td><h:outputText  value="#{solicitacao.projeto}"/> </td>
    			</tr>
    			<tr>
    				<td>Horas Solicitadas</td>
    				<td><h:outputText value="#{solicitacao.qtdHoras}"/> </td>
    			</tr>
    			<tr>
    				<td>Horas Aprovadas</td>
    				<td><h:outputText value="#{solicitacao.qtdAprovadas}"/> </td>
    			</tr>
    			<tr>
    				<td>Observacoes</td>
    				<td><h:inputTextarea value="#{solicitacao.observacoes}" cols="80" rows="6"></h:inputTextarea> </td>
    			</tr>
     
    			<tr>
    				<td colspan="2">
    					<h:commandButton  action="#{funcionarioService.concordar}" value="Concordar"/>
    					<h:commandButton action="#{funcionarioService.resubmeter}" value="ReSubmeter"/>
    				</td>
    			</tr>
    		</table>
    		</h:form>
        </rich:panel>
     
    </ui:define> 
    </ui:composition>

    Tela de decisão do funcionário (clique para fullsize)

    Tela de decisão do funcionário (clique para fullsize)

    Quando pedimos uma re-avaliação, uma nova instância de tarefa será criada na lista do gerente, e o mesmo deve agora re-avaliar (ou não) sua decisão. O ciclo vai continuar até que o gerente resolva cancelar a tarefa ou o funcionário resolva aceitar a decisão do gerente. As telas envolvidas neste processo são as mesmas citadas anteriormente. As duas figuras abaixo mostram o gerente recebendo a solicitação do funcionário, re-avaliando e logo após o funcionário aceitando.

    Gerente re-avaliando sua decisão (clique para fullsize)

    Gerente re-avaliando sua decisão (clique para fullsize)

    Asterix aceitando a nova aprovação do gerente (clique para fullsize)

    Asterix aceitando a nova aprovação do gerente (clique para fullsize)

    Bem, acredito que isso encerra nosso tutorial, acho que faltam apenas dois artefatos para que você possa terminar sua aplicação. O menu e o page.s.xhml. Abaixo o conteúdo de cada um:

    <?xml version="1.0" encoding="UTF-8"?>
    <pages xmlns="http://jboss.com/products/seam/pages"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://jboss.com/products/seam/pages http://jboss.com/products/seam/pages-2.0.xsd"
     
           no-conversation-view-id="/home.xhtml"
           login-view-id="/login.xhtml">
     
        <page view-id="*">
            <navigation>
                <rule if-outcome="home">
                    <redirect view-id="/home.xhtml"/>
                </rule>
            </navigation>
        </page> 
     
        <page view-id="/listarSolicitacoes.xhtml">
        	<navigation from-action="#{gerenteService.avaliarSolicitacao}">
        		<redirect view-id="/exibirSolicitacao.xhtml"></redirect>
        	</navigation>
        </page>
     
        <page view-id="/listarPendencias.xhtml">
        	<navigation from-action="#{funcionarioService.avaliarRetorno}">
        		<redirect view-id="/avaliarSolicitacao.xhtml"></redirect>
        	</navigation>
        </page>
     
        <page view-id="/avaliarSolicitacao.xhtml">
        	<navigation from-action="#{funcionarioService.resubmeter}">
        		<redirect view-id="/listarPendencias.xhtml">
        			<message>Tarefa re-submetida para avaliacao</message>
        		</redirect>
        	</navigation>
        	<navigation from-action="#{funcionarioService.aceitar}">
        		<redirect view-id="/listarPendencias.xhtml">
        			<message>Processo concluido</message>
        		</redirect>
        	</navigation>
        </page>
     
        <page view-id="/solicitarHoras.xhtml">
        	<begin-conversation join="true"/>
        	<navigation from-action="#{funcionarioService.solicitarHoraExtra}">
        		<redirect view-id="/home.xhtml">
        			<message>Solicitacao registrada com sucesso</message>
        		</redirect>
        	</navigation>
        </page>
     
        <page view-id="/exibirSolicitacao.xhtml">
        	<navigation from-action="#{gerenteService.aprovar}">
    			<redirect view-id="/listarSolicitacoes.xhtml">
    				<message>Tarefa aprovada com sucesso</message>
    			</redirect>
        	</navigation>
        	<navigation from-action="#{gerenteService.reprovar}">
    			<redirect view-id="/listarSolicitacoes.xhtml">
    				<message>Tarefa reprovada com sucesso</message>
    			</redirect>
        	</navigation>
     
        </page>
     
        <exception class="org.jboss.seam.framework.EntityNotFoundException">
            <redirect view-id="/error.xhtml">
                <message>Not found</message>
            </redirect>
        </exception>
     
        <exception class="javax.persistence.EntityNotFoundException">
            <redirect view-id="/error.xhtml">
                <message>Not found</message>
            </redirect>
        </exception>
     
        <exception class="javax.persistence.OptimisticLockException">
            <end-conversation/>
            <redirect view-id="/error.xhtml">
                <message>Another user changed the same data, please try again</message>
            </redirect>
        </exception>
     
        <exception class="org.jboss.seam.security.AuthorizationException">
            <redirect view-id="/error.xhtml">
                <message>You don't have permission to do this</message>
            </redirect>
        </exception>
     
        <exception class="org.jboss.seam.security.NotLoggedInException">
            <redirect view-id="/login.xhtml">
                <message>Please log in first</message>
            </redirect>
        </exception>
     
        <exception class="javax.faces.application.ViewExpiredException">
            <redirect view-id="/error.xhtml">
                <message>Your session has timed out, please try again</message>
            </redirect>
        </exception>
     
        <exception>
            <redirect view-id="/error.xhtml">
                <message>Unexpected error, please try again</message>
            </redirect>
        </exception>
     
    </pages>

    <rich:toolBar
        xmlns="http://www.w3.org/1999/xhtml"
        xmlns:ui="http://java.sun.com/jsf/facelets"
        xmlns:h="http://java.sun.com/jsf/html"
        xmlns:f="http://java.sun.com/jsf/core"
        xmlns:s="http://jboss.com/products/seam/taglib"
        xmlns:rich="http://richfaces.org/rich">
        <rich:toolBarGroup>
            <h:outputText value="#{projectName}:"/>
            <s:link view="/home.xhtml" value="Home"/>
            <s:link view="/solicitarHoras.xhtml" value="Solicitar Hora Extra" rendered="#{s:hasRole('user')}"/>
            <s:link view="/listarSolicitacoes.xhtml" value="Verificar Solicitacoes de Hora Extra" rendered="#{s:hasRole('manager')}"/>
            <s:link view="/listarPendencias.xhtml" value="Veriricar Retorno de Solicitacao" rendered="#{s:hasRole('user')}"></s:link>
        </rich:toolBarGroup>
        <!-- @newMenuItem@ -->
        <rich:toolBarGroup location="right">
            <h:outputText value="Welcome, #{identity.username}!" rendered="#{identity.loggedIn}"/>
            <s:link view="/login.xhtml" value="Login" rendered="#{not identity.loggedIn}"/>
            <s:link view="/home.xhtml" action="#{identity.logout}" value="Logout" rendered="#{identity.loggedIn}"/>
        </rich:toolBarGroup>
    </rich:toolBar>

    Acho que para o primeiro post no meu blog isso vale :).

    Nos próximos posts pretendo usar esta aplicação como exemplo para demonstrar outras funcionalidade do Seam e jBPM. Até lá