Archive for the ‘jBPM’ Category.

Garantindo a qualidade de seus serviços

SLAs (Service Level Agreements) são acordos que ocorrem entre duas partes, normalmente entre clientes e provedores de serviço, que garante aos clientes uma qualidade mínima de um serviço. SLAs estão presentes em praticamente todos serviços que contratamos, você por exemplo, ao contratar um provedor de acesso, este deve lhe oferecer em contrato uma qualidade mínima de tempo de dispononibilidade e velocidade de navegação (embora meu provedor tenha a cara de pau de oferecer apenas 20% do valor contratado de garantia de banda).
Quando você estiver modelando o seu processo de negócio, em um determinado momemento de sua especificação, você vai esbarrar em uma SLA, como por exemplo o tempo máximo para um chamado de um cliente ser processado.
Sempre que ministro cursos de jBPM, uma pergunta recorrente é: o jBPM suporta SLA, a resposta é sempre a mesma: Não, o jBPM não suporta SLA, mas lhe oferece todos mecanismos internos para que você defina suas SLAs.
Uma coisa que gostaria de deixar bem claro, é que o jBPM é um excelente framework de GOP (Graph Oriented Programming) e para por ai :). Apesar de eu ser um grande entusiasta do framework, acredito que este nome (BPM) não reflete as características do framework (mas isso é estória para outro post).
SLAs são conceitos muito intrícicos ao seu negócio, colocar isso dentro do framework seria uma abordagem inocente de se tentar abraçar as diversas formas de negócio que existem. Embora alguns produtos de umas empresas de 3 letras >:) clamem que isto seja possível através de plugins e templates específicos por segmento de mercado :D.
Uma outra coisa que deve-se ter em mente quando você estiver mapeando seu processo é: SLAs não são métricas que você extrai do processo, elas são garantias!, é comum os usuários de BPM confundirem SLAs com KPIs (Key Performance Indicators). Você pode sim extrair algumas métricas de uma SLA como ASA (Average Time to Answer) e TAT (Turn Around Time).
Com esta breve introdução de SLAs e o que elas significam no processo, como podemos aplicar isto no nosso processo?
Vamos usar o processo que apresentei nos dois últimos posts para representar uma situação de SLA. Vamos supor que a cada 2 horas a minha tarefa aumente sua prioridade, aumentando assim a chance de a mesma ser vista pelo gerente que deve analisá-la.
Podemos fazer isso com o jBPM através da combinação de um timer e de uma action associada ao mesmo. No nosso processo original, vamos adicionar um timer que execute a cada 2 horas na nossa task. A listagem abaixo mostra isso:

19
20
21
22
23
24
25
26
27
28
<task-node name="Verificacao de Solicitacoes">
		<task name="Verificar Solicitacao">
			<description>
				Solicitacao de Hora extra funcionario #{identity.username}
			</description>
			<assignment pooled-actors="manager"></assignment>
			<timer duedate="2 business hours" repeat="2 business hours">
				<action class="com.furiousbob.action.TaskPrioritySLA"></action>
			</timer>
		</task>

Uma Task pode conter um sub-element timer que no nosso caso deve rodar a cada 2 horas de negócio. O jBPM suporta a noção de hora corrente, ou hora de negócio (business hours) Para maiores informações acesse a documentação do jBPM para timers.

O código responsável por aumentar a prioridade da task é apresentado 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
package com.furiousbob.action;
 
import org.jbpm.graph.def.ActionHandler;
import org.jbpm.graph.exe.ExecutionContext;
import org.jbpm.taskmgmt.exe.TaskInstance;
 
public class TaskPrioritySLA implements ActionHandler {
 
	public void execute(ExecutionContext executionContext) throws Exception {
		try {
			int currentPriority = executionContext.getTaskInstance()
					.getPriority();
			// Maximum priority of a given task is 1
			if (currentPriority > 1) {
				currentPriority--;
				TaskInstance t = executionContext.getJbpmContext()
						.getTaskInstanceForUpdate(
								executionContext.getTaskInstance().getId());
				t.setPriority(currentPriority);
			} else {
				// we could redirect the user to another node in the process if we want to.
 
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
 
	}
 
}

Note que o código simplesmente aumenta a prioridade da task. Para nosso exemplo isto basta. Mas em um exemplo do mundo real você poderia, ao atingir a prioridade máxima, sair do nó corrente e ir para um nó onde o responsável seja alguem do backoffice por exemplo.

Combinando isto com o meu último post você pode criar uma aplicação poderosa onde as tarefas de maior prioridade são sempre exibidas em primeiro lugar, e garante que as tarefas não sofram de starvation, a medida que novas tarefas sejam adicionadas à lista do usuário.

Acredito que este pequeno exemplo possa demonstrar o quão poderoso o jBPM é para lidar com assuntos relacionados a processos de negócio, basta apenas executarmos alguns pequenos ajustes para que o mesmo trabalhe da maneira que gostaríamos.

Tenho na manga alguns tópicos bacanas, para apresentar em relação ao uso do jBPM como suíte completa de BPM, mas antes preciso apresentar alguns outros conceitos, fiquem ligados pois esta semana ainda pretendo apresentar o new kid on the block da jboss o Envers, em uma conversa semana passada com meu amigo Edgar Silva, discutimos umas idéias bacanas para um uso deste framework. Não perca o próximo post.

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é

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á