Skip to content

4. O serviço web J2EE para marcação de consultas

Voltemos à arquitetura da aplicação a construir:

Nesta secção, vamos centrar-nos na construção do serviço web J2EE [1] executado num servidor Sun / Glassfish.

4.1. A base de dados

A base de dados, a que chamaremos [dbrdvmedecins] , é uma base de dados MySQL5 com quatro tabelas:

Image

4.1.1. A tabela [MEDECINS]

Contém informações sobre os médicos geridos pela aplicação [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. Este número é incrementado em 1 sempre que é feita uma alteração na linha.
  • NOM: o apelido do médico
  • PRENOM: o seu nome próprio
  • TITRE: o seu título (Menina, Sra., Sr.)

4.1.2. A tabela [CLIENTS]

Os clientes dos diferentes médicos estão registados 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. Este número é incrementado em 1 sempre que é feita uma alteração na linha.
  • NOM: o nome do cliente
  • PRENOM: o seu nome próprio
  • TITRE: o seu título (Menina, Sra., Sr.)

4.1.3. A tabela [CRENEAUX]

Esta tabela lista os intervalos 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. Este número é incrementado em 1 sempre que é feita uma alteração na linha.
  • ID_MEDECIN: número que identifica o médico a quem pertence este intervalo horário – chave estrangeira na coluna MEDECINS (ID).
  • HDEBUT: hora de início do horário
  • MDEBUT: minutos de início do intervalo
  • HFIN: hora de fim do intervalo
  • MFIN: minutos de fim do intervalo

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

4.1.4. A tabela [RV]

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

  • ID: número que identifica o RV de forma única – chave primária
  • JOUR: dia do RV
  • ID_CRENEAU: intervalo horário do RV – chave estrangeira no campo [ID] da tabela [CRENEAUX] – determina simultaneamente o intervalo horário e o médico em questão.
  • ID_CLIENT: número do cliente para quem é feita a reserva – 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 mais nenhum outro local. Caso contrário, isso significaria que dois RV foram registados ao mesmo tempo para o mesmo médico. Do ponto de vista da programação Java, o controlador JDBC da base de dados lança um SQLException quando esta situação ocorre.

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

4.2. Geração da base de dados

Crie a base de dados MySql [dbrdvmedecins] com a ferramenta da sua escolha. Para criar as tabelas e preenchê-las, pode utilizar o script [createbd.sql] que lhe será fornecido. O 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 da aplicação a construir:

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

  1. por uma camada JPA que permite trabalhar com a BD por meio de objetos
  1. de um EJB encarregado de gerir as operações com a camada JPA
  2. de um serviço web encarregado de expor aos clientes remotos a interface do EJB sob a 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 aceder a um EJB remoto através dos protocolos RMI e JNDI. Na prática, isto limita os clientes a clientes Java. Um serviço web utiliza um protocolo de comunicação padronizado que várias linguagens implementam: .NET, PHP, C++, ... É isso que pretendemos demonstrar aqui, utilizando um cliente .NET.

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

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

  • através de uma classe anotada com @WebService que é executada num contentor web
  • através de um EJB anotado com @WebService que é executado num contentor EJB

Vamos utilizar aqui a primeira solução:

No curso [ref1], parágrafo 14, página 109, encontra-se 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 incluir as bibliotecas Hibernate necessárias para a camada JPA/Hibernate. Se, ao longo do tutorial, verificar que o Glassfish não disponibiliza uma implementação JPA/Hibernate ou se, durante a implementação dos serviços, surgir uma exceção a indicar que as bibliotecas do Hibernate não foram encontradas, 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 controladores JDBC
  • em [3], o controlador JDBC de MySQL

As bibliotecas do Hibernate encontram-se no ficheiro zip que acompanha o tutorial.

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

Voltemos à arquitetura que temos de 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 estes 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.

Descrevemos 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 ligação do NetBeans à base de dados

  • Execute o SGBD e o MySQL 5 para que o BD fique disponível
  • criar uma ligação do NetBeans à base de dados [dbrdvmedecins]
  • no separador [Files], no ramo [Databases] [1], selecionar o controlador JDBC MySQL [2]
  • e, em seguida, selecione a opção [3] «Connect Using», que permite criar uma ligação a uma base de dados MySQL
  • em [4], introduza as informações solicitadas
  • e, em seguida, confirme em [5]
  • em [6], a ligação é criada. Aqui podem ver-se as quatro tabelas da base de dados ligada.

Criação de um projeto EJB

  • em [1], criar uma nova aplicação, um módulo EJB
  • em [2], selecione a categoria [Java EE] e, em [3], o tipo [EJB Module]
  • em [4], selecione uma pasta para o projeto e, em [5], atribua-lhe um nome — em seguida, conclua o assistente
  • em [6] o projeto gerado

Adicionar um recurso JDBC ao servidor Glassfish

Vamos adicionar um recurso JDBC ao servidor Glassfish.

  • no separador [Services], inicie o servidor Glassfish [2, 3]
  • no separador [Projects], clique com o botão direito do rato 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 pretende criar um recurso JDBC, selecionando o tipo [JDBC Resource]
  • em [8], indicar que este recurso JDBC irá utilizar o seu próprio conjunto de ligações
  • em [9], atribuir um nome ao recurso JDBC
  • em [10], passar à etapa seguinte
  • em [11], definem-se as características do conjunto de ligações do recurso JDBC
  • em [12], atribuir um nome ao conjunto de ligações
  • em [13], selecione a ligação NetBeans [dbrdvmedecins] criada anteriormente
  • em [14], avance para o passo seguinte
  • em [15], normalmente não há nada a alterar nesta página. As propriedades da ligação à base de dados MySQL [dbrdvmedecins] foram extraídas das propriedades da ligação do NetBeans [dbrdvmedecins] criada anteriormente
  • em [16], avance para o passo seguinte
  • em [17], mantenha os valores predefinidos sugeridos
  • em [18], conclua o assistente. Este cria o ficheiro [sun-resources.xml] [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 ficheiro acima contém todas as informações introduzidas no assistente no formato XML. Será utilizado pelo IDE 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: indica a implementação JPA utilizada (Toplink, Hibernate, ...) e configura-a.

  • em [1], clique com o botão direito do rato no projeto EJB e selecione [New / Other] em [2]
  • em [3], selecione a categoria [Persistence] e, em seguida, em [4], indique que pretende 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], selecionar o recurso Glassfish «jdbc/dbrdvmedecins» que acabou de ser criado
  • em [8], indicar que não deve ser realizada nenhuma ação na base de dados durante a instanciação da camada JPA
  • concluir o assistente
  • no [9], o ficheiro [persistence.xml] criado pelo assistente

O 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, este ficheiro retoma, no formato XML, as informações fornecidas no assistente. Este ficheiro não é suficiente para trabalhar com a base de dados MySQL5 «dbrdvmedecins». Teríamos de indicar ao Hibernate o tipo de SGBD a gerir. Isso será feito posteriormente.

Criação das entidades JPA

 
  • em [1], clicar com o botão direito do rato no projeto e, em [2], selecionar a opção [New / Other]
  • em [3], selecione a categoria [Persistence] e, em seguida, em [4], indique que pretende criar entidades JPA a partir de uma base de dados existente.
  • em [5], selecione a fonte JDBC «jdbc/dbrdvmedecins» que criámos
  • em [6], as quatro tabelas da base de dados associada
  • em [7,8], inclua-as todas na geração de entidades JPA
  • em [9], continuar com o assistente
  • em [10], as entidades JPA que vão ser geradas
  • em [11], atribuir um nome ao pacote das entidades JPA
  • em [12], escolher o tipo Java que irá encapsular as listas de objetos devolvidas pela camada JPA
  • concluir o assistente
  • em [13], as quatro entidades JPA geradas, uma para cada tabela da base de dados.

Eis, por exemplo, o código da entidade [Rv], que representa uma linha da tabela [rv] da base 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], clicar com o botão direito do rato no projeto e, em [2], selecionar 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-as todas
  • em [7], foram selecionadas
  • em [8], continue com o assistente
  • em [9], atribuir um nome ao pacote dos EJB que vão ser gerados
  • em [10], indicar que os EJB devem implementar tanto uma interface local como uma interface remota
  • Concluir o assistente
  • em [11], os EJB gerados

Eis, por exemplo, o código do EJB que gere o acesso à entidade [Rv], ou seja, à tabela [rv] da base 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 referido, a geração automática de código pode ser muito útil para dar início a um projeto e familiarizar-se com as entidades JPA e EJB. A seguir, reescreveremos as camadas JPA e EJB com o nosso próprio código, mas o leitor encontrará nelas informações que acabámos 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] agrupa 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 todos os passos descritos no parágrafo 4.5. Terá de repetir alguns deles.

4.6.1. Configuração da camada JPA

Recorde-se a arquitetura da nossa aplicação cliente/servidor:

O projeto NetBeans:

 

A camada [JPA] é configurada pelos ficheiros [persistence.xml] e [sun-resources.xml] acima referidos. Estes dois ficheiros são gerados por assistentes já abordados:

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

O ficheiro [persistence.xml] gerado deve ser alterado 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 geridas pelo contentor EJB3 do GlassFish
  • linha 4: é utilizada uma implementação JPA/Hibernate. Para tal, 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. Deve ser adicionada manualmente. Indica ao Hibernate que o SGBD utilizado é o MySQL5.

A fonte de dados «jdbc/dbrdvmedecins» está configurada no seguinte ficheiro [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 da base de dados, nome e palavra-passe do utilizador). A base de dados MySQL dbrdvmedecins é a descrita no parágrafo 4.1.
  • linha 7: as características do conjunto de ligações associado a esta fonte de dados

4.6.2. As entidades da camada JPA

Recorde-se a arquitetura da 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 próprios as entidades. No entanto, estas irão retomar grande parte do código gerado no parágrafo 4.5. Neste caso, pretendemos 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 por predefiniçã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: note-se que a classe [Personne] não é, ela própria, uma entidade (@Entity). Será a classe pai de entidades. A anotação @MappedSuperClass indica esta 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 por predefiniçã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: está associada à tabela [clients]
  • linha 5: 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 por predefiniçã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 por predefiniçã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 - getter
...
}
  • as linhas 15-17 modelam a relação «um para vários» que existe entre a tabela [creneaux] e a tabela [medecins] da base 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 por predefiniçã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] da base de dados e as linhas 18-20 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] da aplicação é 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 - setters
...
}
  • linha 6: a classe deriva da classe [RuntimeException]. O compilador não obriga, portanto, 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 compreender 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] no interior do contentor EJB3 e interceptada por este. Sem a anotação @ApplicationException, o contentor EJB3 encapsula a exceção ocorrida numa exceção do tipo [EjbException] e relança-a. Pode não se desejar este encapsulamento e permitir que uma exceção do tipo [RdvMedecinsException] saia do contentor Ejb3. É isso que permite a anotação @ApplicationException. Além disso, o atributo (rollback=true) desta anotação indica ao contentor EJB3 que, se a exceção do tipo [RdvMedecinsException] ocorrer no interior de um método executado no âmbito de uma transação com um SGBD, esta deve ser revertida. Em termos técnicos, isto denomina-se efetuar um rollback da 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, num determinado dia
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // encontrar um cliente identificado pelo seu ID
  public Client getClientById(Long id);
   // encontrar um cliente identificado pelo seu ID
  public Medecin getMedecinById(Long id);
   // encontrar uma consulta identificada pelo seu ID
  public Rv getRvById(Long id);
   // encontrar um intervalo horário identificado pelo seu ID
  public Creneau getCreneauById(Long id);
   // adicionar um RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // eliminar um RV
  public void supprimerRv(Rv rv);
}

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

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 decorrem no âmbito de uma transação gerida pelo contentor 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 dos 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, num 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
   // intervalo: intervalo horário da consulta
   // cliente: cliente para quem foi marcada a consulta
  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);
    }
  }

   // eliminação de uma consulta
   // consulta: a consulta eliminada
  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 gere o acesso ao contexto de persistência. Aquando da instanciação da classe, este campo será inicializado pelo contentor EJB graças à anotação @PersistenceContext da linha 7.
  • linha 15: consulta JPQL que devolve todas as linhas da tabela [clients] sob a forma de uma lista de objetos [Client].
  • linha 22: consulta semelhante para os médicos
  • linha 32: uma consulta JPQL que realiza uma junção entre as tabelas [creneaux] e [medecins]. É parametrizada pelo ID do médico.
  • linha 43: uma consulta JPQL que realiza uma junção entre as tabelas [rv], [creneaux] e [medecins] e que tem dois parâmetros: o ID do médico e o dia da consulta.
  • linhas 55-57: criação de uma consulta e, em seguida, o seu armazenamento na base de dados.
  • linha 67: eliminação de uma consulta da base de dados.
  • linha 76: executa uma consulta na base de dados para encontrar um determinado cliente
  • linha 85: o mesmo para um médico
  • linha 94: o mesmo para uma consulta
  • linha 103: o mesmo para um intervalo horário
  • Todas as operações com o contexto de persistência em da linha 9 podem encontrar um problema com a base de dados. Por isso, estão todas rodeadas por um try/catch. A eventual exceção é encapsulada na exceção «própria» RdvMedecinsException.

O módulo EJB, uma vez compilado, gera um ficheiro .jar com o :

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

O NetBeans permite implementar facilmente 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á implementado
  • no separador [Services] [3], executa-se o processo [4].
  • em [5], o servidor GlassFish, uma vez iniciado. Ainda não possui nenhum módulo EJB.
  • Inicie o servidor MySQL e certifique-se de que a base de dados [dbrdvmedecins] está online. Para tal, pode utilizar a ligação do NetBeans criada no parágrafo 4.5.
  • No separador [Projects] [6], é implementado 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 implementado aparece na árvore de diretórios do servidor GlassFish
  • Em [9], remove-se o EJB implementado
  • em [10], o EJB já não aparece na árvore de diretórios do servidor GlassFish.

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

Mostramos aqui como implementar um EJB no servidor GlassFish a partir do seu arquivo .jar.

  • Inicie o servidor MySQL e certifique-se de que a base de dados [dbrdvmedecins] está online. Para tal, pode utilizar a ligação do NetBeans criada no parágrafo 4.5.

Recorde-se a configuração JPA do módulo EJB que vai ser implementado. Esta configuração é definida no ficheiro [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, gerida pelo contentor EJB3, denominada «jdbc/dbrdvmedecins».

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

Começamos por eliminar o recurso para o podermos recriar. Fazemo-lo a partir do NetBeans:

  • em [1], as recursos JDBC do servidor GlassFish
  • para [2], o recurso «jdbc/dbrdvmedecins» do nosso EJB
  • em [3], o conjunto de ligações deste recurso JDBC
  • em [4], elimina-se o conjunto de ligações. Isto terá como efeito a eliminação de todos os recursos JDBC que o utilizam, ou seja, o recurso «jdbc/dbrdvmedecins».
  • em [5] e [6], a recurso JDBC e o pool de ligações foram eliminados.

Agora, utilizamos a consola de administração do servidor Glassfish para criar a recurso JDBC e implementar o EJB.

  • no separador [services] [1] do NetBeans, inicie o servidor GlassFish [2] e, em seguida, aceda à sua consola de administração
  • em [4], inicie sessão como administrador (palavra-passe: adminadmin, caso não a tenha alterado durante a instalação ou posteriormente).
  • em [5], selecione o ramo [Connection Pools] dos recursos do GlassFish
  • em [6], crie um novo conjunto de ligações. Recorde-se que um conjunto de ligações é uma técnica para limitar o número de aberturas/encerramentos de ligações com um SGBD. Ao iniciar o servidor, N — um número definido na configuração — ligações são abertas com o SGBD. Estas ligaçõ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 estiver concluída, o EJB devolve a ligação ao conjunto. A ligação nunca é encerrada. É partilhada entre os diferentes threads que acedem ao 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], avance para o passo seguinte
  • No [11], o atributo «Connection Validation Required» faz com que, antes de atribuir uma ligação, o conjunto verifique se esta está operacional. Se não for o caso, cria uma nova. Isto permite que uma aplicação continue a funcionar após uma interrupção momentânea com o SGBD. Durante a interrupção, nenhuma ligação está disponível e são comunicadas exceções ao cliente. Quando a interrupção termina, os clientes que continuam a solicitar ligações voltam a obtê-las: graças ao atributo «Connection Validation Required», todas as ligações do conjunto serão recriadas. Sem este atributo, o conjunto verificaria que as ligações iniciais foram interrompidas, mas não tentaria recriar novas ligações.
  • Em [12], solicita-se o nível de isolamento «Read Committed» para as transações. Este nível garante que uma transação T2 não possa ler dados alterados 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 o URL de BD cujo pool gere as ligações
  • no [16], o utilizador será o root
  • no [17], adicione uma propriedade
  • no [18], adicione a propriedade «Password» com o valor () no [19]. Embora a captura de ecrã [19] não o mostre, não se deve colocar a cadeia vazia, mas sim () (parêntese de abertura, parêntese de fecho) para indicar uma palavra-passe vazia. Se o utilizador root do seu SGBD MySQL tiver uma palavra-passe não vazia, introduza essa palavra-passe.
  • no [20], conclua o assistente de criação do conjunto de ligações para a base de dados MySQL [dbrdvmedecins].
  • em [21], o conjunto de ligações foi criado. Clique na respetiva ligação.
  • em [22], o botão [Ping] permite criar uma ligação à base de dados [dbrdvmedecins]
  • em [23], se tudo correr bem, uma mensagem indica que a ligação foi bem-sucedida

Depois de criado o conjunto de ligações, é possível criar um recurso JDBC:

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

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

  • em [1], selecione o ramo [Enterprise Applications]
  • em [2], com o botão [Deploy], indique que pretende implementar uma nova aplicação
  • em [3], indique que a aplicação é um módulo EJB
  • em [4], selecione o ficheiro JAR do EJB [serveur-ejb-dao-jpa-hibernate.jar] que lhe foi fornecido para o TP.
  • em [5], pode alterar o nome do módulo EJB, se assim o desejar
  • em [6], conclua o assistente de implementação do módulo EJB
  • No [7], o módulo EJB foi implementado. Já pode ser utilizado.

4.9. Testes do EJB da camada [dao]

Agora que o EJB da camada [dao] da nossa aplicação foi implementado, podemos testá-lo. Faremos isso utilizando o 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 ficheiro JAR do EJB da camada [dao] [3] (ver parágrafo 4.6.4).
  • das bibliotecas GlassFish [4] necessárias aos clientes remotos dos EJB.

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);
    }
     // visualizaçã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);
    }
     // visualizaçã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, num 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 provocar 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);
    }
     // eliminar 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 - apresenta 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: note-se a instanciação do proxy do EJB remoto. Utiliza-se o 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 bem-sucedidos:

 

Agora que o EJB da camada [dao] está operacional, podemos avançar para a sua exposição pública através 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 da nossa aplicação cliente/servidor:

Estamos a analisar acima o serviço web da camada [dao]. A única função deste serviço é disponibilizar a interface do EJB da camada [dao] a clientes multiplataforma capazes de interagir com um serviço web.

Recorde-se que existem duas formas de implementar um serviço web:

  • através de uma classe anotada com @WebService que é executada num contentor web
  • através de um EJB anotado com @WebService que é executado num contentor EJB

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

  • o módulo EJB que será executado no contentor EJB: o EJB da camada [dao].
  • o módulo Web, que será executado no contentor Web: o serviço Web que estamos a criar.

Vamos construir este projeto empresarial de duas formas.

4.10.1. Projeto NetBeans - Versão 1

Começamos por criar um projeto NetBeans do tipo «Aplicação 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 este deve ser gerado
  • no [6], define-se o servidor de aplicações que irá executar a aplicação 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 a construir irá utilizar o EJB do projeto anterior [10]. Por isso, precisa de referenciar o ficheiro .jar do módulo EJB [10].
  • No [11], adicionamos um projeto NetBeans às bibliotecas do projeto web [12]
  • no [13], seleciona-se a pasta do módulo EJB no sistema de ficheiros 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 dos 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, num 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
   // intervalo: intervalo horário da consulta
   // cliente: cliente para quem foi marcada a consulta
  @WebMethod
  public Rv ajouterRv(String jour, Creneau creneau, Client client) {
    return dao.ajouterRv(jour, creneau, client);
  }

   // eliminação de uma consulta
   // consulta: a consulta eliminada
  @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]. Recorde-se que esta 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 das consultas de um médico, num determinado dia
  public List<Rv> getRvMedecinJour(Medecin medecin, String jour);
   // encontrar um cliente identificado pelo seu ID
  public Client getClientById(Long id);
   // encontrar um cliente identificado pelo seu ID
  public Medecin getMedecinById(Long id);
   // encontrar uma consulta identificada pelo seu ID
  public Rv getRvById(Long id);
   // encontrar um intervalo horário identificado pelo seu ID
  public Creneau getCreneauById(Long id);
   // adicionar um RV
  public Rv ajouterRv(String jour, Creneau creneau, Client client);
   // eliminar um RV
  public void supprimerRv(Rv rv);
}
  • linha 3: a anotação @WebService transforma a classe [WsDaoJpa] num serviço web.
  • linhas 6-7: a referência do EJB da camada [dao] será injetada pelo servidor de aplicações no campo da linha 7. Recorde-se que é sempre a implementação local (IDaoLocal, neste caso) que é assim injetada. Esta 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 os tornar 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 limita-se a chamar o método M correspondente do EJB injetado na linha 7.

A criação deste serviço web é refletida por um novo ramo no projeto NetBeans:

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

Recorde-se a arquitetura do serviço web em desenvolvimento:

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

  • [1]: o módulo web que acabámos de construir
  • [2]: o módulo EJB que criámos numa etapa anterior e do qual o serviço web depende

Para os implementar em conjunto, é necessário reunir os dois módulos num projeto NetBeans denominado «empresarial»:

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

  • No [4,5], atribui-se um nome ao projeto e define-se a pasta de criação
  • em [6], seleciona-se o servidor de aplicações no qual a aplicação empresarial será implementada
  • em [7], um projeto empresarial pode ter três componentes: aplicação web, módulo EJB, aplicação cliente. Aqui, o projeto é criado sem qualquer componente. Estes serão adicionados posteriormente.
  • em [8], a aplicação empresarial recém-criada.
  • em [9], clicar com o botão direito do rato em [Java EE Modules] e adicionar um novo módulo
  • em [10], apenas os módulos NetBeans atualmente abertos no IDE são apresentados. Aqui, selecionamos o módulo web [serveur-webservice-1-ejb-dao-jpa-hibernate] e o módulo EJB [serveur-ejb-dao-jpa-hibernate] que criámos.
  • No [11], os dois módulos adicionados ao projeto empresarial.

Resta-nos implementar esta aplicação empresarial 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 implementado, este é desativado [2]
  • em [3], implementa-se a aplicação de e-business
  • em [4], a aplicação está implementada. Verifica-se que contém os dois módulos: Web e EJB.

4.10.2. Projeto NetBeans - versão 2

Mostramos agora como implementar o serviço web quando não se dispõe do código-fonte do módulo EJB, mas apenas do 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á analisada
  • [3]: o arquivo EJB da camada [dao], que permite à classe [WsDaoJpa] aceder às definições das diferentes classes, interfaces e entidades das camadas [dao] e [jpa].

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

  • [1], criamos uma aplicação empresarial [ea-rdvmedecins], inicialmente sem qualquer módulo.
  • em [2], adicionamos o módulo web [serveur-webservice-ejb-dao-jpa-hibernate] anterior
  • em [3], o resultado.

Tal como está, a aplicação empresarial [ea-rdvmedecins] não pode ser implementada no servidor Glassfish a partir do NetBeans. É apresentado um erro. É, portanto, necessário implementar manualmente o arquivo EAR da aplicação [ea-rdvmedecins]:

  • o arquivo [ea-rdvmedecins.ear] encontra-se na pasta [dist] [2] do separador [Files] do NetBeans.
  • Nesta pasta [3], encontram-se os dois elementos da aplicação empresarial:
  • o arquivo do EJB [serveur-ejb-dao-jpa-hibernate]. Este 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] é constituído por um simples Build [4] da aplicação empresarial.
  • No [5], a operação de implementação falha.

Para implementar o arquivo [ea-rdvmedecins.ear] da aplicação empresarial, procedemos tal como foi demonstrado durante a implementação do arquivo 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 repetimos os passos já descritos.

Em primeiro lugar, começaremos por «desinstalar» a aplicação empresarial implementada no parágrafo 4.10.1:

  • [1]: selecione o ramo [Enterprise Applications] do servidor Glassfish
  • em [2], selecione a aplicação empresarial a descarregar e, em seguida, em [3], descarregue-a
  • em [4], a aplicação empresarial foi descarregada
  • em [1], selecione o ramo [Enterprise Applications] do servidor Glassfish
  • em [2], implemente uma nova aplicação empresarial
  • em [3], selecione o tipo [Enterprise Application]
  • em [4], indique o ficheiro .ear do projeto NetBeans [ea-rdvmedecins]
  • em [5], implemente este arquivo
  • em [6]; a aplicação foi implementada
  • em [7], o serviço web [WsDaoJpa] aparece no ramo [Web Services] do servidor GlassFish. Selecionamo-lo.
  • em [8], temos várias informações sobre o serviço web. A informação mais interessante para um cliente é a [9]: o 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. Esta URI apresenta uma página de teste. Todos os métodos (@WebMethod) expostos pelo serviço web são apresentados 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. No entanto, é possível verificar que o método getAllClients devolveu efetivamente a lista de clientes. A captura de ecrã mostra-nos que o serviço envia a sua resposta no formato XML.

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

  • em [1] na ferramenta de administração web do servidor Glassfish, selecione o serviço web [WsDaoJpa]
  • em [2], siga a ligação [View WSDL]
  • em [3]: o URI do ficheiro WSDL. Esta é uma informação importante a ter em conta. É necessária para configurar os clientes deste serviço web.
  • em [4], a descrição XML do serviço web. Não iremos comentar este conteúdo complexo.

4.10.3. Testes JUnit do serviço web

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

  • em [1], um projeto Java clássico
  • em [2], a classe de teste
  • em [3], o cliente utiliza o arquivo do EJB para aceder às definições da interface da camada [dao] e das entidades JPA. Recorde-se que este arquivo se encontra na subpasta [dist] da pasta do módulo EJB.

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

No esquema acima, a camada [2] [C=Client] comunica com a camada [1] [S=Serveur]. Para comunicar com a camada [S], o cliente [C] tem de estabelecer uma ligação de rede com a camada [S] e comunicar com ela de acordo com um protocolo específico. As ligações de rede são do tipo TCP e o protocolo de transporte é o HTTP. A camada [S], que representa o serviço web, é implementada por um servlet Java executado pelo servidor Glassfish. Não fomos nós que escrevemos esta servlet. A sua geração é automatizada pelo Glassfish a partir das anotações @Webservice e @WebMethod da classe [WsDaoJpa] que escrevemos. Da mesma forma, vamos automatizar a geração da camada [C] do cliente. A camada [C] é por vezes designada como uma camada proxy do serviço web remoto, sendo que o termo proxy designa um elemento intermediário numa cadeia de software. Neste caso, o proxy C é o intermediário entre o cliente que iremos escrever e o serviço web que implementámos.

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

  • em [1], adicionar um novo elemento ao projeto Java
  • em [2], selecione o ramo [Web services]
  • em [3], selecione [Web Service Client]
  • em [4], forneça o URI do ficheiro WSDL do serviço web. Este URI foi apresentado no parágrafo 4.10.2.
  • em [5], mantenha o valor predefinido [JAX-WS]. O outro valor possível é [JAX-RPC]
  • Após ter validado o assistente de criação do proxy do serviço web, o projeto NetBeans foi enriquecido com um ramo [Web Service References] [6]. Este ramo mostra os métodos expostos pelo serviço web remoto.
  • No separador [Files] [7], foram adicionados códigos-fonte Java [8]. Estes correspondem ao proxy C gerado.
  • Em [9], o código de uma das classes. Pode-se ver em [10] que estas foram colocadas num pacote [rdvmedecins.ws]. Não iremos comentar o código destas classes, que, mais uma vez, é bastante complexo.

Para o cliente Java que estamos a construir, o proxy C gerado serve de intermediário. Para aceder ao método M do serviço web remoto, o cliente Java chama o método M do proxy C. O cliente Java chama, assim, 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 na íntegra.
  • na linha [9], o conteúdo do método [init] foi eliminado.

Nesta fase, o projeto apresenta erros porque o método de teste [test1] utiliza as entidades [Client], [Medecin], [Creneau], [Rv], que já não se encontram nos mesmos pacotes que anteriormente. Estão agora no pacote do proxy C gerado. Eliminam-se as instruções import em questão e regeneram-se através 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 de saber como utilizar o proxy C gerado no nosso código. O NetBeans ajuda-nos neste processo.

  • Selecione, em [1], o método [getAllClients] do serviço web com o rato e, em seguida, arraste esse método para o colocar dentro do método [init] da classe de teste.

O resultado obtido é [2]. Este esboço de código mostra-nos como utilizar 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 o resultado aqui
      java.util.List<rdvmedecins.ws.Client> result = port.getAllClients();
      System.out.println("Result = "+result);
    } catch (Exception ex) {
       // TODO tratar exceções personalizadas aqui
}
  • A linha [5] mostra-nos 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 os 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, pelo que todo o código está envolto num bloco try/catch.
  • Os objetos do proxy C encontram-se no pacote [rdvmedecins.ws]

Depois de compreender este código, percebe-se que a referência local do serviço web remoto pode ser obtida através do código:

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

O código da classe de teste JUnit passa então 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 - apresenta os elementos de uma coleção
  private static void display(String message, List elements) {
 ...
  }
}

Estamos agora prontos para os testes:

No [1], o teste JUnit é executado. No [2], o teste é bem-sucedido. Se observarmos as mensagens na consola do NetBeans, encontramos 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 apresenta os diferentes 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 dos respetivos métodos get/set. Assim, o método toString não foi gerado na entidade [Client] do proxy C. O que explica a exibição anterior. Isto não prejudica o teste JUnit: este foi bem-sucedido. Consideraremos, a partir de agora, que temos um serviço web operacional.