Skip to content

4. O serviço web J2EE de agendamentos

Voltemos à arquitetura do aplicativo a ser desenvolvido:

Nesta seção, vamos nos concentrar na construção do serviço web J2EE [1] executado em um servidor Sun / Glassfish.

4.1. O banco de dados

O banco de dados, que chamaremos de [dbrdvmedecins] , é um banco de dados MySQL5 com quatro tabelas:

Image

4.1.1. A tabela [MEDECINS]

Ela contém informações sobre os médicos gerenciados pelo aplicativo [RdvMedecins].

  • ID: número que identifica o médico — chave primária da tabela
  • VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma alteração é feita na linha.
  • NOM: o sobrenome do médico
  • PRENOM: seu nome
  • TITRE: seu título (Srta., Sra., Sr.)

4.1.2. A tabela [CLIENTS]

Os clientes dos diferentes médicos estão registrados na tabela [CLIENTS]:

  • ID: número de identificação do cliente — chave primária da tabela
  • VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma modificação é feita na linha.
  • NOM: o nome do cliente
  • PRENOM: seu nome
  • TITRE: seu título (Srta., Sra., Sr.)

4.1.3. A tabela [CRENEAUX]

Ela lista os horários em que os RV são possíveis:

  • ID: número que identifica o intervalo horário — chave primária da tabela (linha 8)
  • VERSION: número que identifica a versão da linha na tabela. Esse número é incrementado em 1 sempre que uma alteração é feita na linha.
  • ID_MEDECIN: número que identifica o médico ao qual esse horário pertence – chave estrangeira na coluna MEDECINS (ID).
  • HDEBUT: hora de início do horário
  • MDEBUT: minutos de início do horário
  • HFIN: hora de término do horário
  • MFIN: minutos de término do intervalo

A segunda linha da tabela [CRENEAUX] (ver [1] acima) indica, por exemplo, que o horário nº 2 começa às 8h20 e termina às 8h40 e pertence à médica nº 1 (Sra. Marie PELISSIER).

4.1.4. A tabela [RV]

Ela lista os RV atribuídos a cada médico:

  • ID: número que identifica o RV de forma exclusiva – chave primária
  • JOUR: dia do RV
  • ID_CRENEAU: horário do RV – chave estrangeira no campo [ID] da tabela [CRENEAUX] – define tanto o horário quanto o médico em questão.
  • ID_CLIENT: número do cliente para o qual a reserva foi feita – chave estrangeira no campo [ID] da tabela [CLIENTS]

Esta tabela possui uma restrição de unicidade na sobre os valores das colunas associadas (JOUR, ID_CRENEAU):

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

Se uma linha da tabela [RV] tiver o valor (JOUR1, ID_CRENEAU1) para as colunas (JOUR, ID_CRENEAU), esse valor não pode aparecer em nenhum outro lugar. Caso contrário, isso significaria que dois RV foram registrados ao mesmo tempo para o mesmo médico. Do ponto de vista da programação em Java, o driver JDBC do banco de dados aciona um SQLException quando esse caso ocorre.

A linha de id igual a 3 (ver [1] acima) significa que um RV foi agendado para o horário nº 20 e o cliente nº 4 em 23/08/2006. A tabela [CRENEAUX] nos informa que o horário n.º 20 corresponde ao intervalo das 16h20 às 16h40 e pertence à médica n.º 1 (Sra. Marie PELISSIER). A tabela [CLIENTS] nos informa que o cliente nº 4 é a Srta. Brigitte BISTROU.

4.2. Geração do banco de dados

Crie o banco de dados MySql [dbrdvmedecins] com a ferramenta de sua preferência. Para criar as tabelas e preenchê-las, é possível utilizar o script [createbd.sql], que será fornecido a você. Seu conteúdo é o seguinte:

create table CLIENTS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table CRENEAUX (
        ID bigint not null auto_increment,
        VERSION integer not null,
        HDEBUT integer not null,
        MDEBUT integer not null,
        HFIN integer not null,
        MFIN integer not null,
        ID_MEDECIN bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table MEDECINS (
        ID bigint not null auto_increment,
        VERSION integer not null,
        TITRE varchar(5) not null,
        NOM varchar(30) not null,
        PRENOM varchar(30) not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    create table RV (
        ID bigint not null auto_increment,
        JOUR date not null,
        ID_CLIENT bigint not null,
        ID_CRENEAU bigint not null,
        primary key (ID)
    ) ENGINE=InnoDB;

    alter table CRENEAUX 
        add index FK9BD7A197FE16862 (ID_MEDECIN), 
        add constraint FK9BD7A197FE16862 
        foreign key (ID_MEDECIN) 
        references MEDECINS (ID);

    alter table RV 
        add index FKA4494D97AD2 (ID_CLIENT), 
        add constraint FKA4494D97AD2 
        foreign key (ID_CLIENT) 
        references CLIENTS (ID);

    alter table RV 
        add index FKA441A673246 (ID_CRENEAU), 
        add constraint FKA441A673246 
        foreign key (ID_CRENEAU) 
        references CRENEAUX (ID);

INSERT INTO CLIENTS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'MARTIN', 'Jules', 'Mr');
...

INSERT INTO MEDECINS ( VERSION, NOM, PRENOM, TITRE) VALUES (1, 'PELISSIER', 'Marie', 'Mme');
...

INSERT INTO CRENEAUX ( VERSION, ID_MEDECIN, HDEBUT, MDEBUT, HFIN, MFIN) VALUES (1, 1, 8, 0, 8, 20);
...

INSERT INTO RV ( JOUR, ID_CRENEAU, ID_CLIENT) VALUES ('2006-08-22', 1, 2);
...

ALTER TABLE RV ADD CONSTRAINT UNQ1_RV UNIQUE (JOUR, ID_CRENEAU);

COMMIT WORK;

4.3. Os elementos da arquitetura do lado do servidor

Voltemos à arquitetura do aplicativo a ser desenvolvido:

No lado do servidor, a aplicação será composta por:

  1. de uma camada JPA que permite trabalhar com a BD por meio de objetos
  1. de um EJB responsável por gerenciar as operações com a camada JPA
  2. um serviço web encarregado de expor aos clientes remotos a interface do EJB na forma de um serviço web.

Os elementos (b) e (c) implementam a camada [dao] representada no esquema anterior. Sabe-se que uma aplicação pode acessar um EJB remoto por meio dos protocolos RMI e JNDI. Na prática, isso limita os clientes a clientes Java. Um serviço web utiliza um protocolo de comunicação padronizado que é implementado por diversas linguagens: .NET, PHP, C++, etc. É isso que queremos demonstrar aqui, utilizando um cliente .NET.

Para uma breve introdução aos serviços web, consulte o curso [ref1], parágrafo 14, página 109.

Um serviço web pode ser implementado de duas maneiras:

  • por meio de uma classe anotada com @WebService que é executada em um contêiner web
  • por meio de um EJB anotado com @WebService que é executado em um contêiner EJB

Vamos utilizar aqui a primeira solução:

No curso [ref1], parágrafo 14, página 109, há um exemplo que utiliza a segunda solução.

4.4. Configuração do servidor Glassfish para Hibernate

Dependendo da versão, o servidor Glassfish V2 fornecido com o NetBeans pode não conter as bibliotecas do Hibernate necessárias para a camada JPA/Hibernate. Se, ao longo do tutorial, você perceber que o Glassfish não oferece uma implementação JPA/Hibernate ou que, ao implantar os serviços, uma exceção indicar que as bibliotecas do Hibernate não foram encontradas, você deverá adicionar as bibliotecas à pasta [<glassfish>/domains/domain1/lib/ext] e, em seguida, reiniciar o servidor Glassfish:

  • em [1], a pasta <glassfish>/.../lib/ext
  • em [2], as bibliotecas do Hibernate e alguns drivers JDBC
  • em [3], o driver JDBC de MySQL

As bibliotecas do Hibernate estão no arquivo zip que acompanha o tutorial.

4.5. As ferramentas de geração automática do NetBeans

Voltemos à arquitetura que precisamos construir:

Com o NetBeans, é possível gerar automaticamente a camada [JPA] e a camada [Ejb], que controla o acesso às entidades JPA geradas. É interessante conhecer esses métodos de geração automática, pois o código gerado fornece indicações valiosas sobre como escrever entidades JPA ou o código EJB que as utiliza.

Descreveremos agora algumas dessas ferramentas de geração automática. Para compreender o código gerado, é necessário ter bons conhecimentos sobre as entidades JPA, [ref1] e as EJB, [ref2].

Criação de uma conexão do NetBeans com o banco de dados

  • Execute o SGBD e o MySQL 5 para que o BD fique disponível
  • Crie uma conexão do NetBeans com o banco de dados [dbrdvmedecins]
  • na guia [Files], no ramo [Databases] [1], selecionar o driver JDBC MySQL [2]
  • e, em seguida, selecione a opção [3] “Connect Using”, que permite criar uma conexão com um banco de dados MySQL
  • em [4], forneça as informações solicitadas
  • e, em seguida, confirme em [5]
  • em [6], a conexão é criada. Nela, é possível ver as quatro tabelas do banco de dados conectado.

Criação de um projeto EJB

  • em [1], crie um novo aplicativo, um módulo EJB
  • em [2], selecione a categoria [Java EE] e, em [3], o tipo [EJB Module]
  • em [4], escolha uma pasta para o projeto e, em [5], dê um nome a ele — em seguida, conclua o assistente
  • em [6] o projeto gerado

Adicionando um recurso JDBC ao servidor Glassfish

Vamos adicionar um recurso JDBC ao servidor Glassfish.

  • na guia [Services], inicie o servidor Glassfish [2, 3]
  • na guia [Projects], clique com o botão direito do mouse no projeto EJB e, em [5], selecione a opção [New / Other] que permite adicionar um elemento ao projeto.

Image

  • em [6], selecione a categoria [Glassfish] e, em [7], indique que deseja criar um recurso JDBC, selecionando o tipo [JDBC Resource]
  • em [8], indique que esse recurso JDBC utilizará seu próprio pool de conexões
  • em [9], atribuir um nome ao recurso JDBC
  • em [10], passar para a próxima etapa
  • em [11], definem-se as características do pool de conexões do recurso JDBC
  • em [12], atribua um nome ao pool de conexões
  • em [13], selecione a conexão do NetBeans [dbrdvmedecins] criada anteriormente
  • em [14], avance para a próxima etapa
  • em [15], normalmente não há nada a ser alterado nesta página. As propriedades da conexão com o banco de dados MySQL [dbrdvmedecins] foram extraídas das propriedades da conexão do NetBeans [dbrdvmedecins] criada anteriormente
  • em [16], passe para a próxima etapa
  • em [17], mantenha os valores padrão sugeridos
  • em [18], conclua o assistente. Ele cria os arquivos [sun-resources.xml] e [19], cujo conteúdo é o seguinte:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...">
    <property name="URL" value="jdbc:mysql://localhost:3306/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>

O arquivo acima contém todas as informações inseridas no assistente no formato XML. Ele será utilizado pelo NetBeans para solicitar ao servidor GlassFish que crie o recurso “jdbc/dbrdvmedecins”, definido na linha 4.

Criação de uma unidade de persistência

A unidade de persistência [persistence.xml] configura a camada JPA: ela indica a implementação JPA utilizada (Toplink, Hibernate, ...) e a configura.

  • em [1], clique com o botão direito do mouse no projeto EJB e selecione [New / Other] em [2]
  • em [3], selecione a categoria [Persistence] e, em seguida, em [4], indique que deseja criar uma unidade de persistência JPA
  • em [5], atribua um nome à unidade de persistência criada
  • em [6], selecione [Hibernate] como implementação JPA
  • em [7], selecione o recurso Glassfish “jdbc/dbrdvmedecins” que acabou de ser criado
  • em [8], indicar que nenhuma ação deve ser realizada no banco de dados durante a instanciação da camada JPA
  • concluir o assistente
  • no [9], o arquivo [persistence.xml] criado pelo assistente

Seu conteúdo é o seguinte:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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">
  <persistence-unit name="serveur-ejb-dao-jpa-hibernate-generePU" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <exclude-unlisted-classes>false</exclude-unlisted-classes>
    <properties/>
  </persistence-unit>
</persistence>

Mais uma vez, ele retoma, no formato XML, as informações fornecidas no assistente. Esse arquivo é insuficiente para trabalhar com o banco de dados MySQL5 “dbrdvmedecins”. Precisaríamos indicar ao Hibernate o tipo de SGBD a ser gerenciado. Isso será feito posteriormente.

Criação das entidades JPA

 
  • em [1], clique com o botão direito do mouse no projeto e, em [2], selecione a opção [New / Other]
  • em [3], selecione a categoria [Persistence] e, em seguida, em [4], indique que deseja criar entidades JPA a partir de um banco de dados existente.
  • em [5], selecione a fonte JDBC “jdbc/dbrdvmedecins” que criamos
  • em [6], as quatro tabelas do banco de dados associado
  • em [7,8], inclua todas elas na geração das entidades JPA
  • em [9], continuar com o assistente
  • em [10], as entidades JPA que serão geradas
  • em [11], atribuir um nome ao pacote das entidades JPA
  • em [12], escolher o tipo Java que irá encapsular as listas de objetos retornadas pela camada JPA
  • concluir o assistente
  • em [13], as quatro entidades JPA geradas, uma para cada tabela do banco de dados.

Aqui está, por exemplo, o código da entidade [Rv], que representa uma linha da tabela [rv] do banco de dados [dbrdvmedecins].

package jpa;
...
@Entity
@Table(name = "rv")
public class Rv implements Serializable {
  private static final long serialVersionUID = 1L;
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Basic(optional = false)
  @Column(name = "ID")
  private Long id;
  @Basic(optional = false)
  @Column(name = "JOUR")
  @Temporal(TemporalType.DATE)
  private Date jour;
  @JoinColumn(name = "ID_CRENEAU", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Creneaux idCreneau;
  @JoinColumn(name = "ID_CLIENT", referencedColumnName = "ID")
  @ManyToOne(optional = false)
  private Clients idClient;

  public Rv() {
  }

...
}

Criação da camada EJB de acesso às entidades JPA

  • em [1], clique com o botão direito do mouse no projeto e, em [2], selecione a opção [New / Other]
  • em [3], selecione a categoria [Persistence] e, em seguida, em [4], o tipo [Session Beans for Entity Classes]
  • em [5], as entidades JPA criadas anteriormente são apresentadas
  • em [6], selecione todas elas
  • em [7], elas foram selecionadas
  • em [8], continue com o assistente
  • em [9], atribuir um nome ao pacote dos EJBs que serão gerados
  • em [10], indicar que os EJBs devem implementar tanto uma interface local quanto uma distante
  • concluir o assistente
  • em [11], os EJBs gerados

Aqui está, por exemplo, o código do EJB que gerencia o acesso à entidade [Rv], ou seja, à tabela [rv] do banco de dados [dbrdvmedecins]:

package ejb;
...
@Stateless
public class RvFacade implements RvFacadeLocal, RvFacadeRemote {
  @PersistenceContext
  private EntityManager em;

  public void create(Rv rv) {
    em.persist(rv);
  }

  public void edit(Rv rv) {
    em.merge(rv);
  }

  public void remove(Rv rv) {
    em.remove(em.merge(rv));
  }

  public Rv find(Object id) {
    return em.find(Rv.class, id);
  }

  public List<Rv> findAll() {
    return em.createQuery("select object(o) from Rv as o").getResultList();
  }

}

Como já foi mencionado, a geração automática de código pode ser muito útil para dar início a um projeto e se familiarizar com as entidades JPA e EJB. A seguir, reescreveremos as camadas JPA e EJB com nosso próprio código, mas o leitor encontrará nelas informações que acabamos de ver na geração automática das camadas.

4.6. O projeto NetBeans do módulo EJB

Criamos um novo módulo EJB em branco (ver parágrafo 4.5):

 
  • o pacote [rdvmedecins.entites] reúne as entidades da camada JPA
  • o pacote [rdvmedecins.dao] implementa o EJB da camada [dao]
  • o pacote [rdvmedecins.exceptions] implementa uma classe de exceção específica da aplicação

A seguir, partimos do princípio de que o leitor seguiu todas as etapas do parágrafo 4.5. Ele precisará repetir algumas delas.

4.6.1. Configuração da camada JPA

Vamos relembrar a arquitetura do nosso aplicativo cliente/servidor:

O projeto NetBeans:

 

A camada [JPA] é configurada pelos arquivos [persistence.xml] e [sun-resources.xml] acima. Esses dois arquivos são gerados por assistentes já apresentados:

  • a geração do arquivo [sun-resources.xml] foi descrita no parágrafo 4.5.
  • a geração do arquivo [persistence.xml] foi descrita no parágrafo 4.5.

O arquivo [persistence.xml] gerado deve ser modificado da seguinte forma:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- Dialeto -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>
  • linha 3: o tipo de transações é JTA: as transações serão gerenciadas pelo contêiner EJB3 do GlassFish
  • linha 4: é utilizada uma implementação JPA/Hibernate. Para isso, a biblioteca Hibernate foi adicionada ao servidor GlassFish (ver parágrafo 4.4).
  • linha 5: a fonte de dados JTA utilizada pela camada JPA tem o nome JNDI “jdbc/dbrdvmedecins”.
  • linha 8: esta linha não é gerada automaticamente. Ela deve ser adicionada manualmente. Ela indica ao Hibernate que o SGBD utilizado é o MySQL5.

A fonte de dados “jdbc/dbrdvmedecins” está configurada no seguinte arquivo [sun-resources.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE resources PUBLIC "-//Sun Microsystems, Inc.//DTD Application Server 9.0 Resource Definitions //EN" "http://www.sun.com/software/appserver/dtds/sun-resources_1_3.dtd">
<resources>
  <jdbc-resource enabled="true" jndi-name="jdbc/dbrdvmedecins" object-type="user" pool-name="dbrdvmedecinsPool">
    <description/>
  </jdbc-resource>
  <jdbc-connection-pool ...>
    <property name="URL" value="jdbc:mysql://localhost/dbrdvmedecins"/>
    <property name="User" value="root"/>
    <property name="Password" value="()"/>
  </jdbc-connection-pool>
</resources>
  • linhas 8-10: as características JDBC da fonte de dados (URL do banco de dados, nome e senha do usuário). O banco de dados MySQL dbrdvmedecins é aquele descrito no parágrafo 4.1.
  • linha 7: as características do pool de conexões associado a essa fonte de dados

4.6.2. As entidades da camada JPA

Vale lembrar a arquitetura de nossa aplicação cliente/servidor:

O projeto NetBeans:

O pacote [rdvmedecins.entites] implementa a camada [Jpa].

Vimos no parágrafo 4.5 como gerar automaticamente as entidades JPA de uma aplicação. Aqui, não utilizaremos essa técnica, mas definiremos nós mesmos as entidades. No entanto, elas reterão boa parte do código gerado no parágrafo 4.5. Aqui, queremos que as entidades [Medecin] e [Client] sejam classes filhas de uma classe [Personne].

A classe Pessoa é utilizada para representar médicos e clientes:

package rdvmedecins.entites;
...
@MappedSuperclass
public class Personne implements Serializable {
   // características de uma pessoa

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;

  @Column(name = "TITRE", length = 5, nullable = false)
  private String titre;
  @Column(name = "NOM", length = 30, nullable = false)
  private String nom;
  @Column(name = "PRENOM", length = 30, nullable = false)
  private String prenom;

   // construtor padrão
  public Personne() {
  }

   // construtor com parâmetros
  public Personne(String titre, String nom, String prenom) {
     // utilizando os setters
...
  }

   // construtor por cópia
  public Personne(Personne personne) {
     // utilizando os setters
 ...
  }

   // toString
  @Override
  public String toString() {
    return "[" + titre + "," + prenom + "," + nom + "]";
  }

// getters e setters
....
}
  • linha 3: observe-se que a classe [Personne] não é, por si só, uma entidade (@Entity). Ela será a classe pai das entidades. A anotação @MappedSuperClass indica essa situação.

A entidade [Client] encapsula as linhas da tabela [clients]. Ela deriva da classe anterior [Personne]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CLIENTS")
public class Client extends Personne implements Serializable {

   // construtor padrão
  public Client() {
  }

   // construtor com parâmetros
  public Client(String titre, String nom, String prenom) {
     // pai
    super(titre, nom, prenom);
  }

   // construtor por cópia
  public Client(Client client) {
     // pai
    super(client);
  }
}
  • linha 3: a classe [Client] é uma entidade JPA
  • linha 4: ela está associada à tabela [clients]
  • linha 5: ela deriva da classe [Personne]

A entidade [Medecin], que encapsula as linhas da tabela [medecins], segue o mesmo modelo:

package rdvmedecins.entites;
...
@Entity
@Table(name = "MEDECINS")
public class Medecin extends Personne implements Serializable {

   // construtor padrão
  public Medecin() {
  }

   // construtor com parâmetros
  public Medecin(String titre, String nom, String prenom) {
     // pai
    super(titre, nom, prenom);
  }

   // construtor por cópia
  public Medecin(Medecin medecin) {
     // pai
    super(medecin);
  }
}

A entidade [Creneau] encapsula as linhas da tabela [creneaux]:

package rdvmedecins.entites;
....
@Entity
@Table(name = "CRENEAUX")
public class Creneau implements Serializable {

   // características de um intervalo de RV
  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Version
  @Column(name = "VERSION", nullable = false)
  private Integer version;
  @ManyToOne
  @JoinColumn(name = "ID_MEDECIN", nullable = false)
  private Medecin medecin;
  @Column(name = "HDEBUT", nullable = false)
  private Integer hdebut;
  @Column(name = "MDEBUT", nullable = false)
  private Integer mdebut;
  @Column(name = "HFIN", nullable = false)
  private Integer hfin;
  @Column(name = "MFIN", nullable = false)
  private Integer mfin;

   // fabricante padrão
  public Creneau() {

  }

   // construtor com parâmetros
  public Creneau(Medecin medecin, Integer hDebut,Integer mDebut, Integer hFin, Integer mFin) {
     // utilizando os setters
...
  }

   // construtor por cópia
  public Creneau(Creneau creneau) {
     // utilizando os setters
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + getVersion() + "," + getMedecin() + "," + getHdebut() + ":" + getMdebut() + "," + getHfin() + ":" + getMfin() + "]";
  }

   // setter e getter
...
}
  • as linhas 15-17 modelam a relação “um para vários” que existe entre a tabela [creneaux] e a tabela [medecins] do banco de dados.

A entidade [Rv] encapsula as linhas da tabela [rv]:

package rdvmedecins.entites;
...
@Entity
@Table(name = "RV")
public class Rv implements Serializable {
   // características

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "ID")
  private Long id;
  @Column(name = "JOUR", nullable = false)
  @Temporal(TemporalType.DATE)
  private Date jour;
  @ManyToOne
  @JoinColumn(name = "ID_CLIENT", nullable = false)
  private Client client;
  @ManyToOne
  @JoinColumn(name = "ID_CRENEAU", nullable = false)
  private Creneau creneau;

   // construtor padrão
  public Rv() {
  }

   // construtor com parâmetros
  public Rv(Date jour, Client client, Creneau creneau) {
     // utilizando os setters
...
  }

   // construtor por cópia
  public Rv(Rv rv) {
     // utilizando os setters
...
  }

   // toString
  @Override
  public String toString() {
    return "[" + getId() + "," + new SimpleDateFormat("dd/MM/yyyy").format(getJour()) + "," + getClient() + "," + getCreneau() + "]";
  }

// getters e setters
...
}
  • as linhas 15-17 modelam a relação “um para vários” que existe entre a tabela [rv] e a tabela [clients] do banco de dados, e as linhas 18-20 modelam a relação “um para vários” que existe entre a tabela [rv] e a tabela [creneaux]

4.6.3. A classe de exceção

A classe de exceção [RdvMedecinsException] do aplicativo é a seguinte:

package rdvmedecins.exceptions;

import javax.ejb.ApplicationException;

@ApplicationException(rollback=true)
public class RdvMedecinsException extends RuntimeException {

  private static final long serialVersionUID = 1L;

   // campos privados
  private int code = 0;

   // construtores
  public RdvMedecinsException() {
    super();
  }

  public RdvMedecinsException(String message) {
    super(message);
  }

  public RdvMedecinsException(String message, Throwable cause) {
    super(message, cause);
  }

  public RdvMedecinsException(Throwable cause) {
    super(cause);
  }

  public RdvMedecinsException(String message, int code) {
    super(message);
    setCode(code);
  }

  public RdvMedecinsException(Throwable cause, int code) {
    super(cause);
    setCode(code);
  }

  public RdvMedecinsException(String message, Throwable cause, int code) {
    super(message, cause);
    setCode(code);
  }

   // getters e setters
...
}
  • linha 6: a classe deriva da classe [RuntimeException]. Portanto, o compilador não obriga a tratá-la com try/catch.
  • linha 5: a anotação @ApplicationException faz com que a exceção não seja “engolida” por uma exceção do tipo [EjbException].

Para entender a anotação @ApplicationException, voltemos à arquitetura utilizada no lado do servidor:

A exceção do tipo [RdvMedecinsException] será lançada pelos métodos do EJB da camada [dao] dentro do contêiner EJB3 e interceptada por ele. Sem a anotação @ApplicationException, o contêiner EJB3 encapsula a exceção ocorrida em uma exceção do tipo [EjbException] e a relança. Pode-se não desejar esse encapsulamento e permitir que uma exceção do tipo [RdvMedecinsException] saia do contêiner Ejb3. É isso que a anotação @ApplicationException permite. Além disso, o atributo (rollback=true) dessa anotação indica ao contêiner EJB3 que, se a exceção do tipo [RdvMedecinsException] ocorrer dentro de um método executado em uma transação com um SGBD, essa transação deve ser revertida. Em termos técnicos, isso é chamado de realizar um rollback na transação.

4.6.4. O EJB da camada [dao]

A interface Java [IDao] da camada [dao] é a seguinte:

package rdvmedecins.dao;
...
public interface IDao {

   // lista de clientes
  public List<Client> getAllClients();
   // lista de médicos
  public List<Medecin> getAllMedecins();
   // lista de horários disponíveis de um médico
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // lista de consultas de um médico em um determinado dia
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // localizar um cliente identificado por seu ID
  public Client getClientById(Long id);
   // localizar um cliente identificado por seu ID
  public Medecin getMedecinById(Long id);
   // localizar uma consulta identificada pelo seu ID
  public Rv getRvById(Long id);
   // localizar um horário identificado por seu ID
  public Creneau getCreneauById(Long id);
   // adicionar um RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // excluir um RV
  public void supprimerRv(Rv rv);
}

A interface local [IDaoLocal] do EJB limita-se a derivar da interface [IDao] anterior:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Local;

@Local
public interface IDaoLocal extends IDao{
}

O mesmo se aplica à interface remota [IDaoRemote]:

1
2
3
4
5
6
7
package rdvmedecins.dao;

import javax.ejb.Remote;

@Remote
public interface IDaoRemote extends IDao {
}

O EJB [DaoJpa] implementa ambas as interfaces, a local e a remota:

1
2
3
4
5
6
7
package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {
...
}
  • a linha 3 indica que o EJB remoto tem o nome “rdvmedecins.dao”
  • a linha 4 indica que todos os métodos do EJB são executados dentro de uma transação gerenciada pelo contêiner EJB3.
  • a linha 5 mostra que o EJB implementa as interfaces local e remota.

O código completo do EJB é o seguinte:

package rdvmedecins.dao;
...
@Stateless(mappedName="rdvmedecins.dao")
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class DaoJpa implements IDaoLocal,IDaoRemote {

  @PersistenceContext
  private EntityManager em;

   // lista de clientes
  public List<Client> getAllClients() {
    try {
      return em.createQuery("select c from Client c").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 1);
    }
  }

   // lista de médicos
  public List<Medecin> getAllMedecins() {
    try {
      return em.createQuery("select m from Medecin m").getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 2);
    }
  }

   // lista de horários disponíveis de um determinado médico
   // médico: o médico
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    try {
      return em.createQuery("select c from Creneau c join c.medecin m where m.id=:idMedecin").setParameter("idMedecin", medecin.getId()).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 3);
    }
  }

   // lista de consultas de um determinado médico, em um determinado dia
   // médico: o médico
   // dia: o dia
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    try {
      return em.createQuery("select rv from Rv rv join rv.creneau c join c.medecin m where m.id=:idMedecin and rv.jour=:jour").setParameter("idMedecin", medecin.getId()).setParameter("jour", new SimpleDateFormat("yyyy:MM:dd").parse(jour)).getResultList();
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 4);
    }
  }

   // adição de uma consulta
   // dia: dia da consulta
   // horário: horário da consulta
   // cliente: cliente para o qual a consulta foi marcada
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    try {
      Rv rv = new Rv(new SimpleDateFormat("yyyy:MM:dd").parse(jour), client, creneau);
      em.persist(rv);
      return rv;
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 5);
    }
  }

   // cancelamento de um agendamento
   // consulta: a consulta excluída
  public void supprimerRv(Rv rv) {
    try {
      em.remove(em.merge(rv));
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 6);
    }
  }

   // recuperar um determinado cliente
  public Client getClientById(Long id) {
    try {
      return (Client) em.find(Client.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 7);
    }
  }

   // recuperar um médico específico
  public Medecin getMedecinById(Long id) {
    try {
      return (Medecin) em.find(Medecin.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 8);
    }
  }

   // recuperar uma consulta específica
  public Rv getRvById(Long id) {
    try {
      return (Rv) em.find(Rv.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 9);
    }
  }

   // recuperar um horário específico
  public Creneau getCreneauById(Long id) {
    try {
      return (Creneau) em.find(Creneau.class, id);
    } catch (Throwable th) {
      throw new RdvMedecinsException(th, 10);
    }
  }
}
  • linha 8: o objeto EntityManager, que gerencia o acesso ao contexto de persistência. Ao instanciar a classe, esse campo será inicializado pelo contêiner EJB por meio da anotação @PersistenceContext da linha 7.
  • linha 15: consulta JPQL que retorna todas as linhas da tabela [clients] na forma de uma lista de objetos [Client].
  • linha 22: consulta análoga para os médicos
  • linha 32: uma consulta JPQL que realiza uma junção entre as tabelas [creneaux] e [medecins]. Ela é configurada pelo ID do médico.
  • linha 43: uma consulta JPQL que realiza uma junção entre as tabelas [rv], [creneaux] e [medecins] e possui dois parâmetros: o ID do médico e o dia da consulta.
  • linhas 55-57: criação de uma consulta e, em seguida, seu armazenamento no banco de dados.
  • linha 67: exclusão de uma consulta do banco de dados.
  • linha 76: realiza uma consulta no banco de dados para localizar um determinado cliente
  • linha 85: o mesmo para um médico
  • linha 94: o mesmo para uma consulta
  • linha 103: o mesmo para um horário
  • Todas as operações com o contexto de persistência em da linha 9 podem apresentar um problema com o banco de dados. Por isso, todas elas estão envoltas por um try/catch. A eventual exceção é encapsulada na exceção “própria” RdvMedecinsException.

O módulo EJB, uma vez compilado, gera um arquivo .jar :

4.7. Implantação do EJB da camada [dao] com o NetBeans

O NetBeans permite implantar de forma simples no servidor GlassFish o EJB criado anteriormente.

  • Nas propriedades do projeto EJB, verifique as opções de execução [1].
  • em [2], o nome do servidor no qual o EJB será implantado
  • na guia [Services] [3], inicie-o [4].
  • em [5], o servidor GlassFish, uma vez iniciado. Ele ainda não possui nenhum módulo EJB.
  • Inicie o servidor MySQL e verifique se o banco de dados [dbrdvmedecins] está online. Para isso, você pode usar a conexão do NetBeans criada no parágrafo 4.5.
  • Na guia [Projects] [6], implante o módulo EJB [7]: é necessário que o SGBD MySQL5 esteja em execução para que o recurso JDBC “jdbc/dbrdvmedecins”, utilizado pelo EJB, esteja acessível.
  • No [8], o EJB implantado aparece na árvore de diretórios do servidor GlassFish
  • No [9], remove-se o EJB implantado
  • em [10], o EJB não aparece mais na árvore de diretórios do servidor GlassFish.

4.8. Implantação do EJB da camada [dao] com o GlassFish

Mostramos aqui como implantar um EJB no servidor GlassFish a partir de seu arquivo .jar.

  • Inicie o servidor MySQL e certifique-se de que o banco de dados [dbrdvmedecins] esteja ativo. Para isso, você pode usar a conexão do NetBeans criada no parágrafo 4.5.

Vamos relembrar a configuração JPA do módulo EJB que será implantado. Essa configuração é feita no arquivo [persistence.xml]:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" 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">
  <persistence-unit name="dbrdvmedecins" transaction-type="JTA">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
    <properties>
       <!-- Dialeto -->
      <property name="hibernate.dialect" value="org.hibernate.dialect.MySQL5InnoDBDialect"/>
    </properties>
  </persistence-unit>
</persistence>

A linha 5 indica que a camada JPA utiliza uma fonte de dados JTA, c.a.d, gerenciada pelo contêiner EJB3, denominada “jdbc/dbrdvmedecins”.

Vimos no parágrafo 4.5 como criar esse recurso JDBC a partir do NetBeans. Mostramos aqui como fazer isso diretamente com o GlassFish. Seguimos aqui um procedimento descrito no parágrafo 13.1.2, página 79 do [ref1].

Começamos excluindo o recurso para poder recriá-lo. Fazemos isso a partir do NetBeans:

  • em [1], os recursos JDBC do servidor GlassFish
  • para [2]; o recurso “jdbc/dbrdvmedecins” do nosso EJB
  • em [3], o pool de conexões desse recurso JDBC
  • em [4], excluímos o pool de conexões. Isso terá como efeito a exclusão de todos os recursos JDBC que o utilizam, portanto, o recurso “jdbc/dbrdvmedecins”.
  • Em [5] e [6], o recurso JDBC e o pool de conexões foram excluídos.

Agora, usamos o console de administração do servidor GlassFish para criar o recurso JDBC e implantar o EJB.

  • na guia [services] [1] do NetBeans, inicie o servidor GlassFish [2] e, em seguida, acesse [3] sua console de administração
  • em [4], faça login como administrador (senha: adminadmin, caso não tenha alterado essa senha durante a instalação ou posteriormente).
  • em [5], selecione o ramo [Connection Pools] dos recursos do GlassFish
  • em [6], crie um novo pool de conexões. Vale lembrar que um pool de conexões é uma técnica para limitar o número de aberturas/fechamentos de conexões com um SGBD. Ao iniciar o servidor, N — um número definido na configuração — conexões são abertas com o SGBD. Essas conexões abertas são, em seguida, disponibilizadas aos EJBs que as solicitam para realizar uma operação com o SGBD. Assim que a operação for concluída, o EJB devolve a conexão ao pool. A conexão nunca é fechada. Ela é compartilhada entre as diferentes threads que acessam o SGBD
  • em [7]; atribua um nome ao pool
  • em [8]; a classe que modela a fonte de dados é a classe [javax.sql.DataSource]
  • em [9]; o SGBD que contém a fonte de dados é, neste caso, o MySQl.
  • em [10], passe para a próxima etapa
  • no [11], o atributo “Connection Validation Required” faz com que, antes de fornecer uma conexão, o pool verifique se ela está operacional. Caso contrário, ele cria uma nova. Isso permite que um aplicativo continue funcionando após uma interrupção momentânea com o SGBD. Durante a interrupção, nenhuma conexão está disponível e exceções são reportadas ao cliente. Quando a interrupção termina, os clientes que continuam solicitando conexões passam a obtê-las novamente: graças ao atributo “Connection Validation Required”, todas as conexões do pool serão recriadas. Sem esse atributo, o pool constataria que as conexões iniciais foram interrompidas, mas não tentaria recriar novas conexões.
  • No [12], solicita-se o nível de isolamento “Read Committed” para as transações. Esse nível garante que uma transação T2 não possa ler dados modificados por uma transação T1 enquanto esta última não estiver totalmente concluída.
  • No [13], solicita-se que todas as transações utilizem o nível de isolamento especificado no [12]
  • em [14] e [15], especifique a URL da transação BD cujo pool gerencia as conexões
  • no [16], o usuário será root
  • no [17], adicione uma propriedade
  • no [18], adicione a propriedade “Password” com o valor () no [19]. Embora a captura de tela [19] não mostre isso, não se deve inserir a string vazia, mas sim () (parêntese de abertura, parêntese de fechamento) para indicar uma senha vazia. Se o usuário root do seu SGBD MySQL tiver uma senha que não seja vazia, insira essa senha.
  • no [20], conclua o assistente de criação do pool de conexões para o banco de dados MySQL [dbrdvmedecins].
  • em [21], o pool foi criado. Clique no link correspondente.
  • em [22], o botão [Ping] permite criar uma conexão com o banco de dados [dbrdvmedecins]
  • em [23], se tudo correr bem, uma mensagem indicará que a conexão foi bem-sucedida

Depois que o pool de conexões for criado, é possível criar um recurso JDBC:

  • em [1], seleciona-se o ramo [JDBC Resources] da árvore de objetos do servidor
  • em [2], cria-se um novo recurso JDBC
  • em [3], atribui-se um nome à recurso JDBC. Esse nome deve corresponder ao nome utilizado no arquivo [persistence.xml]:
    <jta-data-source>jdbc/dbrdvmedecins</jta-data-source>
  • no [4], especifica-se o pool de conexões que a nova recurso JDBC deve utilizar: aquele que acabou de ser criado
  • no [5], conclui-se o assistente de criação
  • em [6], o novo recurso JDBC

Agora que o recurso JDBC foi criado, podemos implantar o arquivo jar do EJB:

  • em [1], selecione o ramo [Enterprise Applications]
  • em [2], com o botão [Deploy], indique que deseja implantar uma nova aplicação
  • em [3], indique que a aplicação é um módulo EJB
  • em [4], selecione o arquivo JAR do EJB [serveur-ejb-dao-jpa-hibernate.jar] que lhe foi fornecido para o TP.
  • em [5], você pode alterar o nome do módulo EJB, se desejar
  • em [6], conclua o assistente de implantação do módulo EJB
  • No [7], o módulo EJB foi implantado. Agora ele já pode ser utilizado.

4.9. Testes do EJB da camada [dao]

Agora que o EJB da camada [dao] do nosso aplicativo foi implantado, podemos testá-lo. Faremos isso por meio do seguinte cliente Java:

A classe [MainTestsDaoRemote] [1] é uma classe de teste JUnit 4. As bibliotecas em [2] são constituídas, por um lado:

  • do arquivo JAR do EJB da camada [dao] [3] (ver parágrafo 4.6.4).
  • das bibliotecas GlassFish [4] necessárias para os clientes remotos dos EJBs.

A classe de teste é a seguinte:

package dao;
...
public class MainTestsDaoRemote {

   // camada [dao] testada
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
     // inicialização do ambiente JNDI
    InitialContext initialContext = new InitialContext();
     // instanciação da camada DAO
    dao = (IDaoRemote) initialContext.lookup("rdvmedecins.dao");
  }

  @Test
  public void test1() {
     // dados do teste
    String jour = "2006:08:23";
     // exibição de clientes
    List<Client> clients = null;
    try {
      clients = dao.getAllClients();
      display("Liste des clients :", clients);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // exibição de médicos
    List<Medecin> medecins = null;
    try {
      medecins = dao.getAllMedecins();
      display("Liste des médecins :", medecins);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // exibição dos horários disponíveis de um médico
    Medecin medecin = medecins.get(0);
    List<Creneau> creneaux = null;
    try {
      creneaux = dao.getAllCreneaux(medecin);
      display(String.format("Liste des créneaux du médecin %s", medecin), creneaux);
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // lista de consultas de um médico em um determinado dia
    try {
      display(String.format("Liste des créneaux du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, jour));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // adicionar um RV
    Rv rv = null;
    Creneau creneau = creneaux.get(2);
    Client client = clients.get(0);
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // adicionar um RV no mesmo horário do mesmo dia
     // deve gerar uma exceção
    System.out.println(String.format("Ajout d'un Rv le [%s] dans le créneau %s pour le client %s", jour, creneau, client));
    try {
      rv = dao.ajouterRv(jour, creneau, client);
      System.out.println("Rv ajouté");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
     // excluir um RV
    System.out.println("Suppression du Rv ajouté");
    try {
      dao.supprimerRv(rv);
      System.out.println("Rv supprimé");
      display(String.format("Liste des Rv du médecin %s, le [%s]", medecin, jour), dao.getRvMedecinJour(medecin, "2006:08:23"));
    } catch (Exception ex) {
      System.out.println(ex);
    }
  }

   // método utilitário — exibe os elementos de uma coleção
  private static void display(String message, List elements) {
    System.out.println(message);
    for (Object element : elements) {
      System.out.println(element);
    }
  }
}
  • linha 13: observe a instanciação do proxy do EJB remoto. Utiliza-se seu nome JNDI “rdvmedecins.dao”.
  • Os métodos de teste utilizam os métodos expostos pelo EJB (ver parágrafo 4.6.4).

Se tudo correr bem, os testes devem ser aprovados:

 

Agora que o EJB da camada [dao] está operacional, podemos prosseguir com sua exposição pública por meio de um serviço web.

4.10. O serviço web da camada [dao]

Para uma breve introdução ao conceito de serviço web, consulte o parágrafo 14, página 111 de [ref1].

Voltemos à arquitetura do servidor de nossa aplicação cliente/servidor:

Estamos nos interessando, acima, pelo serviço web da camada [dao]. A única função desse serviço é disponibilizar a interface do EJB da camada [dao] para clientes multiplataforma capazes de se comunicar com um serviço web.

Vale lembrar que há duas maneiras de implementar um serviço web:

  • por meio de uma classe anotada com @WebService que é executada em um contêiner web
  • por meio de um EJB anotado com @WebService que é executado em um contêiner EJB

Aqui, utilizamos a primeira solução. No NetBeans, precisamos criar um projeto de empresa com dois módulos:

  • o módulo EJB que será executado no contêiner EJB: o EJB da camada [dao].
  • o módulo web, que será executado no contêiner web: o serviço web que estamos criando.

Vamos desenvolver esse projeto empresarial de duas maneiras.

4.10.1. Projeto NetBeans - Versão 1

Primeiramente, criamos um projeto NetBeans do tipo “Aplicativo Web”:

  • em [1], criamos um novo projeto na categoria “Java Web” [2] do tipo “Aplicação Web” [3].
  • em [4], atribui-se um nome ao projeto e, em [5], especifica-se a pasta na qual ele deve ser gerado
  • no [6], define-se o servidor de aplicativos que executará o aplicativo web
  • em [7], define-se o contexto da aplicação
  • em [8], valida-se a configuração do projeto.
  • em [9], o projeto gerado. O serviço web que estamos desenvolvendo utilizará o EJB do projeto anterior [10]. Portanto, ele precisa referenciar o arquivo .jar do módulo EJB [10].
  • No [11], adicionamos um projeto do NetBeans às bibliotecas do projeto web [12]
  • no [13], seleciona-se a pasta do módulo EJB no sistema de arquivos e confirma-se.
  • em [14], o módulo EJB foi adicionado às bibliotecas do projeto web.

Em [15], implementamos o serviço web com a seguinte classe [WsDaoJpa]:

package rdvmedecins.ws;
...
@WebService()
public class WsDaoJpa implements IDao {

  @EJB
  private IDaoLocal dao;

   // lista de clientes
  @WebMethod
  public List<Client> getAllClients() {
    return dao.getAllClients();
  }

   // lista de médicos
  @WebMethod
  public List<Medecin> getAllMedecins() {
    return dao.getAllMedecins();
  }

   // lista de horários disponíveis de um determinado médico
   // médico: o médico
  @WebMethod
  public List<Creneau> getAllCreneaux(Medecin medecin) {
    return dao.getAllCreneaux(medecin);
  }

   // lista de consultas de um determinado médico, em um determinado dia
   // médico: o médico
   // dia: o dia
  @WebMethod
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour) {
    return dao.getRvMedecinJour(medecin, jour);
  }

   // adição de uma consulta
   // dia: dia da consulta
   // horário: horário da consulta
   // cliente: cliente para o qual a consulta foi marcada
  @WebMethod
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

   // cancelamento de uma consulta
   // consulta: a consulta excluída
  @WebMethod
  public void supprimerRv(Rv rv) {
    dao.supprimerRv(rv);
  }

   // recuperar um determinado cliente
  @WebMethod
  public Client getClientById(Long id) {
    return dao.getClientById(id);
  }

   // recuperar um médico específico
  @WebMethod
  public Medecin getMedecinById(Long id) {
    return dao.getMedecinById(id);
  }

   // recuperar uma consulta específica
  @WebMethod
  public Rv getRvById(Long id) {
    return dao.getRvById(id);
  }

   // recuperar um horário específico
  @WebMethod
  public Creneau getCreneauById(Long id) {
    return dao.getCreneauById(id);
  }
}
  • Na linha 4, a classe [WsdaoJpa] implementa a interface [IDao]. Vale lembrar que essa interface está definida no arquivo do EJB da camada [dao] da seguinte forma:
package rdvmedecins.dao;
...
public interface IDao {

   // lista de clientes
  public List<Client> getAllClients();
   // lista de médicos
  public List<Medecin> getAllMedecins();
   // lista dos horários disponíveis de um médico
  public List<Creneau> getAllCreneaux(Medecin medecin);
   // lista de consultas de um médico em um determinado dia
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // localizar um cliente identificado por seu ID
  public Client getClientById(Long id);
   // localizar um cliente identificado por seu ID
  public Medecin getMedecinById(Long id);
   // localizar uma consulta identificada pelo seu ID
  public Rv getRvById(Long id);
   // encontrar um horário identificado por seu ID
  public Creneau getCreneauById(Long id);
   // adicionar um RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // excluir um RV
  public void supprimerRv(Rv rv);
}
  • linha 3: a anotação @WebService transforma a classe [WsDaoJpa] em um serviço web.
  • linhas 6-7: a referência do EJB da camada [dao] será injetada pelo servidor de aplicativos no campo da linha 7. Vale lembrar que é sempre a implementação local (IDaoLocal, neste caso) que é injetada dessa forma. Essa injeção é possível porque o serviço web é executado na mesma JVM que o EJB.
  • Todos os métodos do serviço web são marcados com a anotação @WebMethod para torná-los visíveis aos clientes remotos. Um método não marcado com a anotação @WebMethod seria interno ao serviço web e não visível aos clientes remotos. Cada método M do serviço web se limita a chamar o método M correspondente do EJB injetado na linha 7.

A criação desse serviço web é refletida por um novo branch no projeto do NetBeans:

Em [1], vemos o serviço web WsDaoJpa e, em [2], os métodos que ele expõe aos clientes remotos.

Vale lembrar a arquitetura do serviço web em desenvolvimento:

Os componentes do serviço web que vamos implantar são:

  • [1]: o módulo web que acabamos de construir
  • [2]: o módulo EJB que criamos em uma etapa anterior e do qual o serviço web depende

Para implantá-los juntos, é preciso reunir os dois módulos em um projeto do NetBeans denominado “corporativo”:

No [1], criamos um novo projeto corporativo [2, 3].

  • No [4,5], nomeia-se o projeto e define-se a pasta de criação
  • em [6], escolhe-se o servidor de aplicativos no qual a aplicação empresarial será implantada
  • em [7], um projeto corporativo pode ter três componentes: aplicativo web, módulo EJB e aplicativo cliente. Aqui, o projeto é criado sem nenhum componente. Estes serão adicionados posteriormente.
  • em [8], a aplicação corporativa recém-criada.
  • em [9], clique com o botão direito do mouse em [Java EE Modules] e adicione um novo módulo
  • em [10], apenas os módulos do NetBeans atualmente abertos no IDE são exibidos. Aqui, selecionamos o módulo web [serveur-webservice-1-ejb-dao-jpa-hibernate] e o módulo EJB [serveur-ejb-dao-jpa-hibernate] que criamos.
  • No [11], os dois módulos adicionados ao projeto corporativo.

Resta-nos implantar essa aplicação corporativa no servidor GlassFish. Em seguida, o SGBD e o MySQL devem ser iniciados para que a fonte de dados JDBC “jdbc/dbrdvmedecins”, utilizada pelo módulo EJB, fique acessível.

  • no [1], inicia-se o servidor Glassfish
  • se o módulo EJB [serveur-ejb-dao-jpa-hibernate] estiver implantado, ele é desimplantado [2]
  • em [3], a aplicação de e-business é implantada
  • em [4], ela está implantada. Percebe-se que ela contém os dois módulos: Web e EJB.

4.10.2. Projeto NetBeans - versão 2

Mostraremos agora como implantar o serviço web quando não se dispõe do código-fonte do módulo EJB, mas apenas de seu arquivo .jar.

O novo projeto NetBeans do serviço web será o seguinte:

Os elementos dignos de nota do projeto são os seguintes:

  • [1]: o serviço web é implementado por um projeto NetBeans do tipo [Web Application].
  • [2]: o serviço web é implementado pela classe [WsDaoJpa], já estudada
  • [3]: o arquivo EJB da camada [dao], que permite que a classe [WsDaoJpa] tenha acesso às definições das diferentes classes, interfaces e entidades das camadas [dao] e [jpa].

Em seguida, elaboramos o projeto empresarial necessário para a implantação do serviço web:

  • [1], criamos um aplicativo corporativo [ea-rdvmedecins], inicialmente sem nenhum módulo.
  • em [2], adicionamos o módulo web [serveur-webservice-ejb-dao-jpa-hibernate] anterior
  • em [3], o resultado.

Da forma como está, a aplicação corporativa [ea-rdvmedecins] não pode ser implantada no servidor Glassfish a partir do NetBeans. Ocorre um erro. Portanto, é necessário implantar manualmente o arquivo EAR da aplicação [ea-rdvmedecins]:

  • o arquivo [ea-rdvmedecins.ear] está localizado na pasta [dist] [2] da guia [Files] do NetBeans.
  • Nesse arquivo [3], encontram-se os dois elementos do aplicativo corporativo:
  • o arquivo do EJB [serveur-ejb-dao-jpa-hibernate]. Esse arquivo está presente porque fazia parte das bibliotecas referenciadas pelo serviço web.
  • o arquivo do serviço web [serveur-webservice- ejb-dao-jpa-hibernate].
  • O arquivo [ea-rdvmedecins.ear] é composto por um simples Build e [4] da aplicação corporativa.
  • No [5], a operação de implantação falha.

Para implantar o arquivo [ea-rdvmedecins.ear] do aplicativo corporativo, procedemos da mesma forma que foi mostrado durante a implantação do arquivo do EJB [serveur-ejb-dao-jpa-hibernate.jar] no parágrafo 4.2. Utilizamos novamente o cliente web de administração do servidor GlassFish. Não repetiremos as etapas já descritas.

Primeiramente, começaremos por “desinstalar” a aplicação corporativa implantada no parágrafo 4.10.1:

  • [1]: selecione o ramo [Enterprise Applications] do servidor Glassfish
  • em [2], selecione o aplicativo corporativo a ser desinstalado e, em seguida, em [3], desinstale-o
  • em [4], a aplicação corporativa foi descarregada
  • em [1], escolha o ramo [Enterprise Applications] do servidor Glassfish
  • em [2], implante um novo aplicativo corporativo
  • em [3], selecione o tipo [Enterprise Application]
  • em [4], especifique o arquivo .ear do projeto NetBeans [ea-rdvmedecins]
  • em [5], implante esse arquivo
  • em [6]; o aplicativo foi implantado
  • em [7]; o serviço web [WsDaoJpa] aparece no ramo [Web Services] do servidor GlassFish. Selecione-o.
  • em [8], temos diversas informações sobre o serviço web. A mais interessante para um cliente é a informação [9]: a URI do serviço web.
  • Em [10], é possível testar o serviço web
  • em [11], a URI do serviço web, à qual foi adicionado o parâmetro ?tester. Essa URI exibe uma página de teste. Todos os métodos (@WebMethod) expostos pelo serviço web são exibidos e podem ser testados. Aqui, testamos o método [13], que solicita a lista de clientes.
  • No [14], apresentamos apenas uma visão parcial da página de resposta. Mas dá para ver que o método getAllClients realmente retornou a lista de clientes. A captura de tela mostra que ele envia sua resposta no formato XML.

Um serviço web é descrito na íntegra por um arquivo XML denominado arquivo WSDL:

  • em [1] na ferramenta de administração web do servidor Glassfish, selecione o serviço web [WsDaoJpa]
  • em [2], siga o link [View WSDL]
  • em [3]: a URI do arquivo WSDL. É importante conhecer essa informação. Ela é necessária para configurar os clientes desse serviço web.
  • em [4], a descrição XML do serviço web. Não faremos comentários sobre esse conteúdo complexo.

4.10.3. Testes JUnit do serviço web

Criamos um projeto no NetBeans para “executar” os testes já realizados com um cliente EJB, desta vez utilizando um cliente para o serviço web recentemente implantado. Seguimos aqui um procedimento semelhante ao descrito no parágrafo 14.2.1, página 115 do [ref1].

  • em [1], um projeto Java clássico
  • em [2], a classe de teste
  • em [3], o cliente utiliza o arquivo do EJB para acessar as definições da interface da camada [dao] e das entidades JPA. Vale lembrar que esse arquivo está na subpasta [dist] da pasta do módulo EJB.

Para acessar o serviço web remoto, é necessário gerar classes proxy:

No esquema acima, a camada [2] [C=Client] se comunica com a camada [1] [S=Serveur]. Para se comunicar com a camada [S], o cliente [C] precisa estabelecer uma conexão de rede com a camada [S] e se comunicar com ela de acordo com um protocolo específico. As conexões de rede são do tipo TCP e o protocolo de transporte é HTTP. A camada [S], que representa o serviço web, é implementada por um servlet Java executado pelo servidor Glassfish. Não escrevemos essa servlet. Sua geração é automatizada pelo Glassfish a partir das anotações @Webservice e @WebMethod da classe [WsDaoJpa] que escrevemos. Da mesma forma, automatizaremos a geração da camada [C] do cliente. Às vezes, a camada [C] é chamada de camada proxy do serviço web remoto, sendo que o termo proxy designa um elemento intermediário em uma cadeia de software. Aqui, o proxy C é o intermediário entre o cliente que vamos escrever e o serviço web que implantamos.

Com o NetBeans 6.5, o proxy C pode ser gerado da seguinte maneira (para prosseguir, é necessário que o serviço web esteja ativo no servidor GlassFish):

  • em [1], adicione um novo elemento ao projeto Java
  • em [2], selecione o ramo [Web services]
  • em [3], selecione [Web Service Client]
  • em [4], forneça a URI do arquivo WSDL do serviço web. Essa URI foi apresentada no parágrafo 4.10.2.
  • em [5], mantenha o valor padrão [JAX-WS]. O outro valor possível é [JAX-RPC]
  • Após confirmar o assistente de criação do proxy do serviço web, o projeto do NetBeans foi ampliado com um ramo [Web Service References] [6]. Esse ramo mostra os métodos expostos pelo serviço web remoto.
  • na guia [Files] [7], foram adicionados códigos-fonte Java [8]. Eles correspondem ao proxy C gerado.
  • Em [9], o código de uma das classes. Percebe-se em [10] que elas foram colocadas em um pacote [rdvmedecins.ws]. Não comentaremos o código dessas classes, que, mais uma vez, é bastante complexo.

Para o cliente Java que estamos construindo, o proxy C gerado atua como intermediário. Para acessar o método M do serviço web remoto, o cliente Java chama o método M do proxy C. Assim, o cliente Java chama métodos locais (executados na mesma JVM) e, de forma transparente para ele, essas chamadas locais são traduzidas em chamadas remotas.

Resta-nos saber como chamar os métodos M do proxy C. Voltemos à nossa classe de teste JUnit:

Em [1], a classe de teste [MainTestsDaoRemote] é a mesma já utilizada no teste do EJB da camada [dao]:

package dao;
...
public class MainTestsDaoRemote {

   // camada [dao] testada
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
  }

  @Test
  public void test1() {
...
  }
}
  • na linha [13], o teste test1 é mantido exatamente igual.
  • na linha [9], o conteúdo do método [init] foi excluído.

Nesta fase, o projeto apresenta erros, pois o método de teste [test1] utiliza as entidades [Client], [Medecin], [Creneau], [Rv], que não estão mais nos mesmos pacotes de antes. Elas estão no pacote do proxy C gerado. Removemos as instruções import em questão e as regeneramos por meio da operação “Fix Imports”.

Voltemos ao código da classe de teste [MainTestsDaoRemote]:

package dao;
...

public class MainTestsDaoRemote {

   // camada [dao] testada
  private static IDaoRemote dao;

  @BeforeClass
  public static void init() throws NamingException {
}

O método [init] da linha 10 deve inicializar a referência da camada [dao] da linha 7. Precisamos saber como utilizar o proxy C gerado em nosso código. O NetBeans nos ajuda nesse processo.

  • Selecione, no [1], o método [getAllClients] do serviço web com o mouse e, em seguida, arraste esse método para soltá-lo dentro do método [init] da classe de teste.

O resultado obtido é [2]. Esse esboço de código mostra como usar o proxy C gerado:

1
2
3
4
5
6
7
8
9
    try { // Operação de chamada ao serviço web
      rdvmedecins.ws.WsDaoJpaService service = new rdvmedecins.ws.WsDaoJpaService();
      rdvmedecins.ws.WsDaoJpa port = service.getWsDaoJpaPort();
       // TODO processar resultado aqui
      java.util.List<rdvmedecins.ws.Client> result = port.getAllClients();
      System.out.println("Result = "+result);
    } catch (Exception ex) {
       // TODO: tratamento de exceções personalizadas aqui
}
  • A linha [5] mostra que o método [getAllClients] é um método do objeto do tipo [WsDaoJpa] definido na linha 3. O tipo [WsDaoJpa] é uma interface que apresenta os mesmos métodos que aqueles expostos pelo serviço web remoto.
  • Na linha [3], o objeto [WsDaoJpa port] é obtido a partir de outro objeto do tipo [WsDaoJpaService], definido na linha 2. O tipo [WsDaoJpaService] representa o proxy C gerado localmente.
  • O acesso ao serviço web remoto pode falhar; por isso, todo o código está envolto por um try/catch.
  • Os objetos do proxy C estão no pacote [rdvmedecins.ws]

Depois de entender esse código, percebe-se que a referência local do serviço web remoto pode ser obtida por meio do código:

WsDaoJpa dao=new WsDaoJpaService().getWsDaoJpaPort();

O código da classe de teste JUnit passa a ser o seguinte:

package dao;

import rdvmedecins.ws.Client;
import rdvmedecins.ws.Creneau;
import rdvmedecins.ws.Medecin;
import rdvmedecins.ws.Rv;
import rdvmedecins.ws.WsDaoJpa;
import rdvmedecins.ws.WsDaoJpaService;
...

public class MainTestsDaoRemote {

   // camada [dao] testada
  private static WsDaoJpa dao;

  @BeforeClass
  public static void init(){
    dao=new WsDaoJpaService().getWsDaoJpaPort();
  }

  @Test
  public void test1() {
...
  }

   // método utilitário — exibe os elementos de uma coleção
  private static void display(String message, List elements) {
 ...
  }
}

Agora estamos prontos para os testes:

No [1], o teste JUnit é executado. No [2], ele é aprovado. Se observarmos as mensagens exibidas no console do NetBeans, encontraremos linhas como as seguintes:

Liste des clients :
rdvmedecins.ws.Client@1982fc1
rdvmedecins.ws.Client@676437
rdvmedecins.ws.Client@1e4853f
rdvmedecins.ws.Client@1e808ca

No lado do servidor, a entidade [Client] possui um método toString que exibe os diversos campos de um objeto do tipo [Client]. Durante a geração automática do proxy C, as entidades são criadas no proxy C, mas apenas com os campos privados acompanhados de seus métodos get/set. Assim, o método toString não foi gerado na entidade [Client] do proxy C. Isso explica a exibição anterior. Isso não prejudica o teste JUnit: ele foi bem-sucedido. Consideraremos, a partir de agora, que temos um serviço web operacional.