Skip to content

10. Construção de aplicativos distribuídos CORBA

10.1. Introdução

No capítulo anterior, vimos como criar aplicativos distribuídos em Java com o pacote RMI. Abordamos aqui o mesmo problema, desta vez com a arquitetura CORBA. CORBA (Common Object Request Broker Architecture) é uma especificação definida pelo OMG (Object Management Group), que reúne diversas empresas do setor de TI. A CORBA define um “barramento de software” acessível a aplicativos escritos em diferentes linguagens:

Image

Veremos que a construção de uma aplicação distribuída com CORBA é semelhante ao método empregado com Java RMI: os conceitos são parecidos. O CORBA apresenta a vantagem da interoperabilidade com aplicativos escritos em outras linguagens.

10.2. Processo de desenvolvimento de uma aplicação CORBA

10.2.1. Introdução

Para desenvolver uma aplicação cliente-servidor CORBA, seguiremos as seguintes etapas:

  1. escrita da interface do servidor com IDL (Interface Definition Language)
  2. geração das classes “esqueleto” e “stub” do servidor
  3. criação do servidor
  4. criação do cliente
  5. compilação de todas as classes
  6. inicialização de um diretório de serviços CORBA
  7. inicialização do servidor
  8. inicialização do cliente

Tomaremos como primeiro exemplo o servidor de eco já utilizado no contexto RMI. Assim, o leitor poderá observar as diferenças entre os dois métodos.

O aplicativo foi testado com o jdk1.2.

10.2.2. Criação da interface do servidor

Assim como no Java RMI, em relação ao cliente, o servidor é definido por sua interface. Se as classes que implementam o servidor não são necessárias para o cliente, as classes de sua interface, por outro lado, são. Enquanto o Java RMI utilizava uma interface Java que gerava as classes “esqueleto” e “stub” do servidor, a arquitetura Java CORBA requer que a interface seja descrita em uma linguagem diferente do Java. Essa interface dará origem a várias classes, algumas das quais serão utilizadas pelo cliente e outras pelo servidor.

A descrição da interface de eco será a seguinte:

module echo{
    interface iSrvEcho{
        string echo(in string msg);
    };
};

A descrição da interface será armazenada em um arquivo echo.idl. Ela é escrita na linguagem IDL (Interface Definition Language) do OMG. Para ser utilizável, ela deve ser analisada por um programa que criará arquivos-fonte na linguagem utilizada para desenvolver a aplicação CORBA. Aqui, utilizaremos o programa idltojava.exe, que, a partir da interface anterior, criará os arquivos-fonte .java necessários para a aplicação. O programa idltojava.exe não vem junto com o JDK. Ele pode ser obtido no site da Sun http://java.sun.com.

Vamos analisar algumas linhas da interface idl anterior:

module echo

é equivalente ao pacote echo do Java. A compilação da interface resultará no pacote Java echo c.a.d, um diretório contendo classes Java.

interface iSrvEcho

é equivalente à interface iSrvEcho do Java. Gerará uma interface Java.

string echo(in string msg)

é equivalente à instrução Java String echo(String msg). Os tipos da linguagem IDL não correspondem exatamente aos da linguagem Java. As correspondências serão apresentadas um pouco mais adiante neste capítulo. Na linguagem IDL, os parâmetros de uma função podem ser de entrada (in), de saída (out) ou de entrada-saída (inout). Aqui, o método echo recebe um parâmetro de entrada msg, que é uma sequência de caracteres, e retorna uma sequência de caracteres como resultado.

A interface anterior é a do nosso servidor de eco. Vale lembrar que uma interface remota descreve os métodos do objeto servidor acessíveis aos clientes. Aqui, apenas o método echo estará disponível para os clientes.

10.2.3. Compilação da interface IDL do servidor

Uma vez definida a interface do servidor, geramos os arquivos Java correspondentes.

E:\data\java\corba\ECHO>dir *.idl

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL

E:\data\java\corba\ECHO>d:\javaidl\idltojava.exe -fno-cpp echo.idl

A opção -fno-cpp serve para indicar que não se deve utilizar um pré-processador (usado com mais frequência com C/C++). A compilação do arquivo echo.idl gera um subdiretório echo contendo os seguintes arquivos:

E:\data\java\corba\ECHO>dir echo

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java

O arquivo iSrvEcho.java é o arquivo Java que descreve a interface do servidor:

/*  * Arquivo: ./ECHO/ISRVECHO.JAVA  * De: ECHO.IDL  * Data: Seg 15 de março 13:56:08 1999  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package echo;
public interface iSrvEcho
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String echo(String msg)
;
}

Percebe-se que se trata praticamente de uma tradução literal da interface IDL. Se tivermos curiosidade em examinar o conteúdo dos outros arquivos .java, encontraremos elementos mais complexos. Veja a seguir o que diz a documentação sobre a função desses diferentes arquivos:

iSrvEcho.java

a interface do servidor

_iSrvEchoImplbase.java

implementa a interface iSrvEcho anterior. Trata-se de uma classe abstrata, o “esqueleto” do servidor, que fornece ao servidor as funcionalidades CORBA necessárias para a aplicação distribuída.

_iSrvEchoStub.java

Esta é a imagem (“stub”) do servidor que será utilizada pelo cliente. Ela fornece ao cliente as funcionalidades CORBA para acessar o servidor.

iSrvEchoHelper.java

Fornece os métodos necessários para o gerenciamento de referências de objetos CORBA

iSrvEchoHolder.java

Fornece os métodos necessários para o gerenciamento dos parâmetros de entrada e saída dos métodos da interface.

10.2.4. Compilação das classes geradas a partir da interface IDL

É recomendável compilar as classes anteriores. Veremos em outro exemplo que é possível detectar aqui erros decorrentes de um funcionamento incorreto do gerador idltojava. Neste caso, tudo correu bem e, após a compilação, temos no diretório do pacote echo os seguintes arquivos:

E:\data\java\corba\ECHO\echo>dir

_ISRVE~1 JAV         1 095  17/03/99  17:19 _iSrvEchoStub.java
ISRVEC~1 JAV           311  17/03/99  17:19 iSrvEcho.java
ISRVEC~2 JAV           825  17/03/99  17:19 iSrvEchoHolder.java
ISRVEC~3 JAV         1 827  17/03/99  17:19 iSrvEchoHelper.java
_ISRVE~2 JAV         1 803  17/03/99  17:19 _iSrvEchoImplBase.java
_ISRVE~1 CLA         2 275  18/03/99  11:25 _iSrvEchoImplBase.class
_ISRVE~2 CLA         1 383  18/03/99  11:25 _iSrvEchoStub.class
ISRVEC~1 CLA           251  18/03/99  11:25 iSrvEcho.class
ISRVEC~2 CLA         2 078  18/03/99  11:25 iSrvEchoHelper.class
ISRVEC~3 CLA           858  18/03/99  11:25 iSrvEchoHolder.class

10.2.5. Gravação no servidor

10.2.5.1. Implementação da interface iSrvEcho

Definimos acima a interface iSrvEcho. Agora, vamos escrever a classe que implementa essa interface. Ela será derivada da classe _iSrvEchoImplbase.java, que, conforme indicado acima, já implementa a interface iSrvEcho.

// pacotes importados
import echo.*;

// classe que implementa o eco remoto
public class srvEcho extends _iSrvEchoImplBase{
    // método que realiza o eco
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fim do eco
}// fim da classe

O código é autoexplicativo. Essa classe está registrada no arquivo srvEcho.java, no diretório pai do pacote da interface iSrvEcho.

É possível compilar para verificar:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac srvEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.5.2. Registro da classe de criação do servidor

Assim como em uma aplicação cliente-servidor RMI, um servidor CORBA deve ser registrado em um diretório para que possa ser acessado pelos clientes. É esse procedimento de registro que, no nível de desenvolvimento, difere dependendo se se trata de um aplicativo CORBA ou RMI. A seguir, o código do servidor CORBA de eco registrado no arquivo serveurEcho.java:

// pacotes importados
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- classe serveurEcho
public class serveurEcho{
    // ------- main: inicia o servidor de eco
    // sintaxe pg machineAnnuaire portAnnuaire nomService
    // máquina: máquina que suporta o diretório CORBA
    // porta: porta do diretório CORBA
    // nomService: nome do serviço a ser registrado

  public static void main(String arg[]){
    // os argumentos estão presentes?
    if(arg.length!=3){
        System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomService");
        System.exit(1);
    }
    // recuperamos os argumentos
    String machine=arg[0];
    String port=arg[1];
    String nomService=arg[2];

    try{

    // precisamos de um objeto CORBA para trabalhar
    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
      ORB orb=ORB.init(initORB,null);
    // colocamos o serviço no diretório de serviços
    // ele se chamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // criamos o servidor e o associamos ao serviço srvEcho
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // acompanhamento
    System.out.println("Serveur d'écho prêt");
    // aguardando solicitações dos clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
    // ocorreu um erro
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// manual
}// serveurEcho

A seguir, explicamos as linhas gerais da inicialização do servidor, sem entrar em detalhes que, à primeira vista, podem parecer complexos. É importante reter as linhas gerais do exemplo anterior, pois elas se repetem em todos os servidores CORBA.

10.2.5.2.1. Os parâmetros do servidor

Um servidor CORBA deve se registrar em um serviço de diretório que opera em uma máquina e em uma porta específicas. Nossa aplicação receberá esses dois dados como parâmetros. O serviço assim registrado deve ter um nome, que será o terceiro parâmetro.

10.2.5.2.2. Criar o objeto de acesso ao serviço de diretório CORBA

Para acessar o serviço de diretório e registrar nosso servidor de eco, precisamos de um objeto chamado ORB (Object Request Broker), obtido com o seguinte método de classe:

ORB ORB.init(String [] args, Properties prop)

Args :     tableau de paires de chaînes de caractères, chaque paire étant de la forme (paramètre,valeur)

Prop :     propriétés de l’application

O exemplo utiliza a seguinte sequência para obter o objeto ORB:


    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
       ORB orb=ORB.init(initORB,null);

Os pares (parâmetro, valor) utilizados são os seguintes:


("-ORBInitialHost",machine) : ce couple précise la machine ou opère l’annuaire des services CORBA, ici la machine passée en paramètre au serveur.

("-ORBInitialPort",port ) : ce couple précise le port ou opère l’annuaire des services CORBA, ici le port passé en paramètre au serveur.

O segundo parâmetro do método init é definido como null. Se o primeiro parâmetro também tivesse sido definido como null, o par (máquina, porta) utilizado teria sido o padrão (localhost,900).

10.2.5.2.3. Registrar o servidor no diretório de serviços CORBA

O registro do servidor no diretório é feito por meio das seguintes operações:

    // o serviço está sendo adicionado ao diretório de serviços
    // ele se chamará srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // criamos o servidor e o associamos ao serviço srvEcho
    srvEcho serveurEcho=new srvEcho();
          ncRef.rebind(path,serveurEcho);
            orb.connect(serveurEcho);

A primeira parte do código consiste em preparar o nome do serviço. Esse nome é representado no código pela variável path. O nome de um serviço é composto por vários elementos:

  • um componente inicial objRef, um objeto genérico que deve ser convertido para o tipo NamingContext, neste caso ncRef.
  • o nome do serviço, neste caso nomService, que foi passado como parâmetro ao servidor

Esses componentes do nome (NameComponent) são reunidos em uma matriz, neste caso path. É essa matriz que “nomeia” de forma precisa o serviço criado. Uma vez criado o nome, resta

  • associá-lo a uma instância do servidor (a classe srvEcho criada anteriormente)

srvEcho serveurEcho=new srvEcho();
  • registrá-la no diretório
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilação da classe de inicialização do servidor

Compilamos a classe anterior:

E:\data\java\corba\ECHO>j:\jdk12\bin\javac serveurEcho.java

E:\data\java\corba\ECHO>dir

ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.6. Texto do cliente

10.2.6.1. O código

Estamos escrevendo um cliente para testar nosso serviço de eco. Passaremos ao cliente os mesmos três parâmetros que passamos ao servidor:

Máquina: máquina onde está localizado o diretório de serviços CORBA

Porta: porta na qual esse diretório opera

nomService: nome do serviço de eco

O cliente se conecta ao serviço de eco e, em seguida, solicita que o usuário digite mensagens no teclado. Essas mensagens são enviadas ao servidor de eco, que as reenvia. O acompanhamento desse diálogo é feito na tela.

O cliente CORBA do serviço de eco é muito semelhante ao cliente RMI já desenvolvido. Mais uma vez, o cliente precisa se conectar a um serviço de diretório para obter uma referência do objeto-servidor ao qual deseja se conectar. A diferença entre os dois clientes reside exatamente nisso e somente nisso. Aqui está o código do cliente de eco CORBA:


    

10.2.6.2. A conexão do cliente ao servidor

O cliente CORBA acima se conecta ao servidor por meio da instrução:

        // estabelece-se a conexão com o servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

Após essa operação, o cliente obtém uma referência do servidor do écho. A partir daí, um cliente CORBA não difere de um cliente RMI. O método privado que garante a conexão com o servidor é o seguinte:

// pacotes importados
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

// ---------- classe cltEcho
public class cltEcho {

    public static void main(String arg[]){
         // sintaxe: cltEcho machineAnnuaire portAnnuaire nomeserviço
         // máquina: máquina na qual o diretório de serviços está em operação CORBA
         // porta: porta em que o diretório de serviços opera
         // nomService: nome do serviço de eco

         // verificação dos argumentos
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
            System.exit(1);
        }

         // recuperação dos parâmetros
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

         // estabelece-se a conexão com o servidor de eco
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

         // diálogo cliente-servidor
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        iSrvEcho serveur=null;

        try{
             // abertura do fluxo do teclado
            in=new BufferedReader(new InputStreamReader(System.in));
             // ciclo de leitura das mensagens a serem enviadas ao servidor de eco
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                 // envio da mensagem ao servidor e recebimento da resposta
                reponse=serveurEcho.echo(msg);
                 // acompanhamento
                System.out.println("Réponse serveur : " + reponse);
                 // próxima mensagem
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
             // concluído
            System.exit(0);
         // gestão de erros         
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// main

     // ---------------------- getServeurEcho
    private static iSrvEcho getServeurEcho(String machine, String port, 
            String nomService){

         // solicita uma referência do servidor de eco
         // rastreamento
        System.out.println("--> Connexion au serveur CORBA en cours...");
         // a referência do servidor de eco
        iSrvEcho serveurEcho=null;
        try{
             // é solicitado um objeto CORBA para trabalhar
            String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
          ORB orb=ORB.init(initORB,null);
             // utiliza-se o serviço de diretório para localizar o servidor de eco
          org.omg.CORBA.Object objRef=
                orb.resolve_initial_references("NameService");
          NamingContext ncRef=NamingContextHelper.narrow(objRef);
             // o serviço procurado se chama srvEcho — ele é solicitado
          NameComponent nc= new NameComponent(nomService,"");
          NameComponent path[]={nc};
          serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur d'écho ("
                + e + ")");
            System.exit(10);
        }// try-catch
         // retornamos a referência ao servidor
        return serveurEcho;
    }// getServeurEcho

}// classe

Encontramos as mesmas sequências de código que no servidor:

  • criamos um objeto ORB que nos permitirá acessar o diretório de serviços CORBA
String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
      ORB orb=ORB.init(initORB,null);
  • definimos os diferentes componentes do nome do serviço de eco
        org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
  • solicitamos ao serviço de diretório uma referência do serviço de eco (é nesse ponto que nos diferenciamos do servidor)
serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

10.2.6.3. Compilation

E:\data\java\corba\ECHO>j:\jdk12\bin\javac cltEcho.java

E:\data\java\corba\ECHO>dir

CLTECH~1 CLA         2 599  18/03/99  13:51 cltEcho.class
CLTECH~1 JAV         2 907  16/03/99  16:15 cltEcho.java
ECHO     IDL            78  15/03/99  13:56 ECHO.IDL
SERVEU~1 CLA         1 793  18/03/99  13:18 serveurEcho.class
SERVEU~1 JAV         1 806  16/03/99  15:38 serveurEcho.java
SRVECH~1 CLA           488  18/03/99  11:30 srvEcho.class
SRVECH~1 JAV           252  15/03/99  14:02 srvEcho.java
ECHO           <REP>        17/03/99  17:19 echo

10.2.7. Testes

10.2.7.1. Inicialização do serviço de diretório

Em um computador com Windows, iniciamos o serviço de diretório da seguinte maneira:

E:\data\java\corba\ECHO>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

o que faz com que o serviço de diretório seja iniciado na porta 1000 do computador.

O serviço de diretório tnameserv exibe uma mensagem na tela semelhante à seguinte:

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
044700000018afabcafe000000027620dd9a000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

Não está muito legível, mas vamos nos concentrar na última linha: o serviço está ativo na porta 1000.

10.2.7.2. Lançamento do servidor de eco

O serviço de eco é iniciado com três parâmetros:


E:\data\java\corba\ECHO>start j:\jdk12\bin\java serveurEcho localhost 1000 srvEcho

O servidor exibe:

    Serveur d’écho prêt

10.2.7.3. Inicialização do cliente no mesmo computador que o servidor


E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho localhost 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : msg1
Réponse serveur : [msg1]
Message : msg2
Réponse serveur : [msg2]
Message : fin

10.2.7.4. Inicialização do cliente em um computador Windows diferente do servidor

E:\data\java\corba\ECHO>j:\jdk12\bin\java cltEcho tahe.istia.univ-angers.fr 1000 srvEcho
--> Connexion au serveur CORBA en cours...
Message : abcd
Réponse serveur : [abcd]
Message : efgh
Réponse serveur : [efgh]
Message : fin

10.3. Exemplo 2: um servidor SQL

10.3.1. Introdução

Retomamos aqui a descrição do servidor SQL, já analisado no contexto do Java RMI, sempre com o objetivo de destacar os pontos em comum entre os dois métodos, bem como suas diferenças. Recordamos a função desse servidor SQL: ele está instalado em uma máquina Windows e permite que clientes remotos acessem os bancos de dados públicos ODBC desse computador Windows.

Image

O cliente CORBA poderia realizar três operações:

  • conectar-se ao banco de dados de sua escolha
  • enviar consultas SQL
  • encerrar a conexão

O servidor executa as consultas SQL do cliente e envia os resultados a ele. Essa é sua função principal e é por isso que o chamamos de servidor SQL. Aplicamos as diferentes etapas vistas anteriormente com o servidor de eco.

10.3.2. Criação da interface IDL do servidor

Vale lembrar a interface RMI que utilizamos para o servidor:

import java.rmi.*;

// a interface remota
public interface interSQL extends Remote{
    public String connect(String pilote, String url, String id, String mdp)
        throws java.rmi.RemoteException;
    public String[] executeSQL(String requete, String separateur) 
        throws java.rmi.RemoteException;
    public String close()
        throws java.rmi.RemoteException;
}

A função dos diferentes métodos era a seguinte:

Connect: o cliente se conecta a um banco de dados remoto, fornecendo o driver, a URL JDBC, bem como sua identidade (id) e senha (mdp) para acessar esse banco. O servidor retorna uma sequência de caracteres indicando o resultado da conexão:

    200 - Connexion réussie
    500 - Echec de la connexion

executeSQL: o cliente solicita a execução de uma consulta SQL no banco de dados ao qual está conectado. Ele indica o caractere que deve separar os campos nos resultados que lhe são retornados. O servidor retorna uma matriz de cadeias de caracteres:

    100 n

para uma consulta de atualização do banco de dados, sendo n o número de linhas atualizadas

    500 msg d’erreur

se a consulta gerou um erro

    501 Pas de résultats

se a consulta não gerou nenhum resultado

    101 ligne1
    101 ligne2
    101 ...

se a consulta gerou resultados. As linhas assim retornadas pelo servidor são as linhas de resultados da consulta.

Close: o cliente encerra sua conexão com o banco de dados remoto. O servidor retorna uma string indicando o resultado desse encerramento:

    200 Base fermée
    500 Erreur lors de la fermeture de la base (msg d’erreur)

A interface IDL do servidor será a seguinte:

module srvSQL{

    typedef sequence<string> resultats;

    interface interSQL{
        string connect(in string pilote, in string urlBase, in string id, in string mdp);
        resultats executeSQL(in string requete, in string separateur);
        string close();
    };// interface
};// módulo

A única novidade em relação ao que vimos na interface IDL do servidor de eco é o uso da palavra-chave sequence. Essa palavra-chave permite definir um array unidimensional. A definição é feita em duas etapas:

  • definição de um tipo para designar a matriz, neste caso “resultados”:
typedef sequence<string> resultats;

A palavra-chave typedef é bem conhecida pelos programadores de C/C++: ela permite definir um novo tipo. Aqui, o tipo resultats é definido como equivalente ao tipo sequence<string>, c.a.d, que é uma matriz dinâmica (sem dimensão definida) de cadeias de caracteres.

  • Utilização do novo tipo onde for necessário
resultats executeSQL(in string requete, in string separateur);

O método executeSQL, portanto, retorna um array de strings.

10.3.3. Compilação da interface IDL do servidor

A interface IDL anterior é inserida no arquivo srvSQL.idl. Compilamos esse arquivo:

E:\data\java\corba\sql>d:\javaidl\idltojava -fno-cpp srvSQL.idl

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL

Percebe-se que a compilação gerou uma pasta com o nome do módulo da interface IDL (srvSQL). Vamos dar uma olhada no conteúdo dessa pasta:

E:\data\java\corba\sql>dir srvSQl

RESULT~1 JAV           833  19/03/99  10:00 resultatsHolder.java
RESULT~2 JAV         1 883  19/03/99  10:00 resultatsHelper.java
_INTER~1 JAV         2 474  19/03/99  10:00 _interSQLStub.java
INTERS~1 JAV           448  19/03/99  10:00 interSQL.java
INTERS~2 JAV           841  19/03/99  10:00 interSQLHolder.java
INTERS~3 JAV         1 855  19/03/99  10:00 interSQLHelper.java
_INTER~2 JAV         4 535  19/03/99  10:00 _interSQLImplBase.java

Vale lembrar que os arquivos Helper e Holder são classes relacionadas aos parâmetros de entrada e saída e aos resultados dos métodos da interface remota. O diretório srvSQL contém todos os arquivos .java relacionados à interface interSQL definida no arquivo .idl. Ele também contém arquivos relacionados ao tipo resultats criado na interface IDL.

O arquivo interSQL.java é o arquivo Java da interface do nosso servidor. É importante verificar se o que foi gerado automaticamente corresponde às nossas expectativas. O arquivo interSQL.java gerado é o seguinte:

/*  * Arquivo: ./SRVSQL/INTERSQL.JAVA  * De: SRVSQL.IDL  * Data: Sexta-feira, 19 de março, 09:59:48 de 1999  *   Por: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 18 de agosto de 1998 16:25:34  */

package srvSQL;
public interface interSQL
    extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity {
    String connect(String pilote, String urlBase, String id, String mdp)
;
    String[] executeSQL(String requete, String separateur)
;
    String close()
;
}

Percebemos que temos a mesma interface utilizada para o cliente-servidor RMI. Portanto, podemos prosseguir. Vamos compilar todos esses arquivos .java:

E:\data\java\corba\sql\srvSQL>j:\jdk12\bin\javac *.java

Note: _interSQLImplBase.java uses or overrides a deprecated API.  Recompile with
 "-deprecation" for details.
1 warning

E:\data\java\corba\sql\srvSQL>dir *.class

_INTER~1 CLA         3 094  19/03/99  10:01 _interSQLImplBase.class
_INTER~2 CLA         1 953  19/03/99  10:01 _interSQLStub.class
INTERS~1 CLA           430  19/03/99  10:01 interSQL.class
INTERS~2 CLA         2 096  19/03/99  10:01 interSQLHelper.class
INTERS~3 CLA           870  19/03/99  10:01 interSQLHolder.class
RESULT~1 CLA         2 047  19/03/99  10:01 resultatsHelper.class
RESULT~2 CLA           881  19/03/99  10:01 resultatsHolder.class

10.3.4. Código do servidor SQL

Agora, vamos escrever o código do servidor SQL. Vale lembrar que essa classe deve derivar da classe abstrata _nomInterfaceImplBase, gerada pela compilação do arquivo IDL. Exceto por essa particularidade e se excluirmos as sequências de código relacionadas ao registro do serviço em um diretório, o código do servidor CORBA é idêntico ao do servidor RMI:

// pacotes importados
import java.sql.*;
import java.util.*;
import srvSQL.*;

// classe SQLServant
public class SQLServant extends _interSQLImplBase{

    // dados globais da classe
    private Connection DB;

    // --------------- conexão
    public String connect(String pilote, String url, String id,
        String mdp){

        // conexão à base de dados url por meio do driver
        // identificação com ID e senha

        String resultat=null;            // resultado do método
        try{
            // carregamento do driver
            Class.forName(pilote);
            // solicitação de conexão
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // erro
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // fim
        return resultat;
    }            

    // ------------- executeSQL
    public String[] executeSQL(String requete, String separateur){

        // executa uma consulta SQL no banco de dados DB
        // e armazena os resultados em uma matriz de strings

        // dados necessários para a execução da consulta
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // criação do contêiner da consulta
            S=DB.createStatement();
            // execução da consulta
            if (! S.execute(requete)){
                // consulta de atualização
                // é retornado o número de linhas atualizadas
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // era uma consulta
            // recuperando os resultados
            RS=S.getResultSet();
            // número de campos do Resultset
            int nbChamps=RS.getMetaData().getColumnCount();
            // eles são processados
            while(RS.next()){
                // criação da linha de resultados
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // adição ao vetor de resultados
                resultats.addElement(ligne);
            }// while
            // fim da análise dos resultados
            // liberamos os recursos
            RS.close();
            S.close();
            // retorno dos resultados
            int nbLignes=resultats.size();
            if (nbLignes==0){
                lignes=new String[1];
                lignes[0]="501 Pas de résultats";
            } else {
                lignes=new String[resultats.size()];
                for(int i=0;i<lignes.length;i++)
                    lignes[i]=(String) resultats.elementAt(i);
            }//if
            return lignes;
        } catch (Exception e){
            // erro
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- fechar
    public String close(){
        // fecha a conexão com o banco de dados
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // retorno do resultado
        return resultat;
    }
}// classe SQLServant

Essa classe está localizada no arquivo SQLServant.java, que estamos compilando:

E:\data\java\corba\sql>dir

SRVSQL   IDL           275  19/03/99   9:59 srvSQL.idl
SRVSQL         <REP>        19/03/99   9:41 srvSQL
SQLSER~1 JAV         2 941  15/03/99   9:09 SQLServant.java

E:\data\java\corba\sql>j:\jdk12\bin\javac SQLServant.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class

10.3.5. Criação do programa de inicialização do servidor SQL

A classe anterior representa o servidor SQL após sua inicialização. Antes disso, ele deve ser registrado no diretório de serviços CORBA. Assim como no serviço de eco, faremos isso por meio de uma classe especial à qual passaremos, no momento da execução, três parâmetros:

Máquina: máquina onde se encontra o diretório de serviços CORBA

Porta: porta na qual esse diretório opera

nomService: nome do serviço SQL

O código dessa classe é praticamente idêntico ao da classe que fazia a mesma coisa para o serviço de eco. Destacamos em negrito a linha que difere entre as duas classes: ela não cria o mesmo objeto-servidor. Vemos, portanto, que ainda temos a mesma mecânica de inicialização do servidor. Se isolarmos essa mecânica em uma classe, como foi feito aqui, ela se torna praticamente transparente para o desenvolvedor.

// pacotes importados
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- classe serveurSQL
public class serveurSQL{
    // ------- main: inicia o servidor SQL
  public static void main(String arg[]){
        // serveurSQL serviço de porta da máquina

        //: o número de argumentos está correto?
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
            System.exit(1);
        }
        // recuperamos os argumentos
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
    try{
            // precisamos de um objeto CORBA para trabalhar
      ORB orb=ORB.init(initORB,null);
            // colocamos o serviço no diretório de serviços
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
        // criamos o servidor e o associamos ao serviço srvSQL
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // acompanhamento
        System.out.println("Serveur SQL prêt");
        // aguardando solicitações dos clientes
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
            // ocorreu um erro
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// manual
}// srvSQL

Compilamos essa nova classe:

E:\data\java\corba\sql>j:\jdk12\bin\javac serveurSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class

10.3.6. Gravação do cliente

O cliente do servidor CORBA é chamado com os seguintes parâmetros:

    machine port nomServiceAnnuaire pilote urlBase id mdp separateur

máquina: máquina onde está localizado o diretório de serviços CORBA

porta: porta na qual esse diretório opera

nomService: nome do serviço SQL

driver: driver que o servidor SQL deve utilizar para gerenciar o banco de dados desejado

urlBase: URL JDBC do banco de dados a ser gerenciado

id: identidade do cliente ou null se não houver identidade

mdp: senha do cliente ou nulo se não houver senha

separador: caractere que o servidor SQL deve usar para separar os campos das linhas de resultados de uma consulta

Aqui está um exemplo de parâmetros possíveis:

    localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,

onde:

máquina: máquina na qual está localizado o diretório de serviços CORBA

porta: porta na qual esse diretório opera

srvSQL: srvSQL, nome CORBA do servidor SQL

driver: sun.jdbc.odbc.JdbcOdbcDriver, o driver padrão para bancos de dados com interface ODBC

urlBase: jdbc:odbc:articles, para utilizar um banco de dados “articles” declarado na lista de bancos de dados públicos ODBC da máquina Windows

id: null, sem identificação

senha: null, sem senha

separador: , os campos dos resultados serão separados por uma vírgula

Uma vez iniciado com os parâmetros anteriores, o cliente segue as etapas a seguir:

  • ele se conecta à máquina machine na porta port para solicitar o serviço CORBA srvSQL
  • solicita a conexão com o banco de dados de artigos
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • solicita que o usuário digite uma consulta SQL no teclado
  • ela a envia ao servidor SQL
executeSQL(requete, ’’,’’);
  • exibe na tela os resultados retornados pelo servidor
  • ele solicita novamente que o usuário digite uma consulta SQL no teclado. Ele será encerrado quando a consulta terminar.

Segue-se o código Java do cliente. Os comentários devem ser suficientes para sua compreensão. Observa-se que:

  • o código é idêntico ao do cliente RMI já estudado. Ele difere neste último pelo processo de solicitação do serviço ao diretório, processo isolado no método getServeurSQL.
  • O método getServeurSQL é idêntico ao escrito para o cliente de eco

Portanto, vemos que:

  • um cliente CORBA difere de um cliente RMI apenas pela forma como se conecta ao servidor
  • essa forma é idêntica para todos os clientes CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;

public class clientSQL {

    // dados globais da classe
    private static String syntaxe =
        "syntaxe : cltSQL machine port service pilote urlBase id mdp separateur";
    private static BufferedReader in=null;
    private static interSQL serveurSQL=null;

    public static void main(String arg[]){
        // sintaxe: cltSQL máquina porta separador driver URL ID senha
        // máquina porta: máquina e porta do diretório de serviços CORBA a ser contatado
        // serviço: nome do serviço
        // driver: driver a ser utilizado para o banco de dados a ser consultado
        // urlBase: URL JDBC do banco de dados a ser utilizado
        // id: identificação do usuário
        // mdp: sua senha
        // separador: string que separa os campos nos resultados de uma consulta

        // verificação do número de argumentos
        if(arg.length!=8)
            erreur(syntaxe,1);

        // inicialização dos parâmetros de conexão com o banco de dados
        String machine=arg[0];
        String port=arg[1];
        String service=arg[2];        
        String pilote=arg[3];
        String urlBase=arg[4];
        String id, mdp, separateur;
        if(arg[5].equals("null")) id=""; else id=arg[5];
        if(arg[6].equals("null")) mdp=""; else mdp=arg[6];        
        if(arg[7].equals("null")) separateur=" "; else separateur=arg[7];        

        // parâmetros do serviço de diretório CORBA
        String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
        // cliente CORBA — solicitando uma referência do servidor SQL
        interSQL serveurSQL=getServeurSQL(machine,port,service);

        // diálogo cliente-servidor
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // abertura do fluxo do teclado
            in=new BufferedReader(new InputStreamReader(System.in));
            // rastreamento
            System.out.println("--> Connexion à la base de données en cours");
            // solicitação de conexão inicial ao banco de dados
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // acompanhamento
            System.out.println("<-- "+reponse);
            // análise da resposta
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // ciclo de leitura das consultas a serem enviadas ao servidor SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // envio da solicitação ao servidor e recebimento da resposta
                lignes=serveurSQL.executeSQL(requete,separateur);
                // acompanhamento
                afficheLignes(lignes);
                // próxima solicitação
                System.out.print("--> Requête : ");                
                requete=in.readLine().toLowerCase().trim();
            }// while
            // acompanhamento
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // encerramento da conexão
            reponse=serveurSQL.close();
            // próximo
            System.out.println("<-- " + reponse);
            // fim
            System.exit(0);
        // gestão de erros        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// tentar
    }// main

    // ----------- AfficheLignes
    private static void afficheLignes(String[] lignes){
        for (int i=0;i<lignes.length;i++)
            System.out.println("<-- " + lignes[i]);
    }// afficheLignes

    // ------------ erro
    private static void erreur(String msg, int exitCode){
        // exibição da mensagem de erro
        System.err.println(msg);
        // possível liberação de recursos
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // saindo
        System.exit(exitCode);
    }// erro

    // ---------------------- getServeurSQL
    private static interSQL getServeurSQL(String machine, String port, String service){
        // solicita uma referência do servidor SQL
        // máquina: máquina do diretório de serviços CORBA
        // porta: porta do diretório de serviços CORBA
        // serviço: nome do serviço CORBA a ser solicitado

        // acompanhamento
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // a referência do servidor SQL
        interSQL serveurSQL=null;
        // parâmetros do serviço de diretório CORBA
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
                try{
            // solicita-se um objeto CORBA para trabalhar — para isso, utiliza-se a porta 
            // de escuta do diretório de serviços CORBA
      ORB orb=ORB.init(initORB,null);
            // utiliza-se o serviço de diretório para localizar o servidor SQL
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // o serviço procurado se chama srvSQL — solicitamos esse serviço
      NameComponent nc= new NameComponent(service,"");
      NameComponent path[]={nc};
      serveurSQL=interSQLHelper.narrow(ncRef.resolve(path));
        } catch (Exception e){
            System.err.println("Erreur lors de la localisation du serveur SQL ("
                + e + ")");
            System.exit(10);
        }// try-catch
        // retornamos a referência ao servidor
        return serveurSQL;
    }// getServeurSQL

}// classe

Vamos compilar a classe do cliente:

E:\data\java\corba\sql>j:\jdk12\bin\javac clientSQL.java

E:\data\java\corba\sql>dir *.class

SQLSER~1 CLA         2 568  19/03/99  10:19 SQLServant.class
SERVEU~1 CLA         1 800  19/03/99  10:33 serveurSQL.class
CLIENT~1 CLA         3 774  19/03/99  10:45 clientSQL.class

Estamos prontos para os testes.

10.3.7. Testes

10.3.7.1. Pré-requis

Supõe-se que um banco de dados chamado “Artigos” esteja disponível publicamente na máquina Windows do servidor:

Image

Essa base de dados tem a seguinte estrutura:

nome
tipo
code
código do artigo com 4 caracteres
nom
nome (sequência de caracteres)
prix
seu preço (real)
stock_actu
seu estoque atual (número inteiro)
stock_mini
o estoque mínimo (número inteiro) abaixo do qual é necessário reabastecer o item

10.3.7.2. Lançamento do serviço de diretório

E:\data\java\corba\sql>start j:\jdk12\bin\tnameserv -ORBInitialPort 1000

Le service d’annuaire est lancé sur le port 1000. Il affiche dans une fenêtre DOS quelque chose du genre :

Initial Naming Context:
IOR:000000000000002849444c3a6f6d672e6f72672f436f734e616d696e672f4e616d696e67436f
6e746578743a312e3000000000010000000000000030000100000000000a69737469612d30303900
052800000018afabcafe000000027693d3fd000000080000000000000000
TransientNameServer: setting port for initial object references to: 1000

10.3.7.3. Inicialização do servidor SQL

Iniciando o servidor SQL:

E:\data\java\corba\sql>start j:\jdk12\bin\java serveurSQL localhost 1000 srvSQL

Ele exibe em uma janela DOS:

    Serveur SQL prêt

10.3.7.4. Inicialização de um cliente na mesma máquina que o servidor

Aqui estão os resultados obtidos com um cliente na mesma máquina que o servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL localhost 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select nome, stock_actu, stock_mini from artigos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 cachalot,6,6
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Consulta: delete from artigos where stock_mini<7
<-- 100 1
--> Consulta: selecione nome, stock_actu, stock_mini da tabela artigos
<-- 101 vélo,31,8
<-- 101 arc,9,8
<-- 101 canoé,7,7
<-- 101 fusil,9,8
<-- 101 skis nautiques,13,8
<-- 101 essai3,13,9
<-- 101 léopard,7,7
<-- 101 panthère,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.3.7.5. Execução de um cliente em uma máquina diferente daquela do servidor

Aqui estão os resultados obtidos com um cliente em uma máquina diferente daquela do servidor:

E:\data\java\corba\sql>j:\jdk12\bin\java clientSQL tahe.istia.univ-angers.fr 1000 srvSQL sun.jdbc.odbc.JdbcOdbcDriver jdbc:odbc:articles null null ,
--> Connexion au serveur CORBA en cours...
--> Connexion à la base de données en cours
<-- 200 Connexion réussie
--> Consulta: select * from artigos
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoé,1502,7,7
<-- 101 x123,fusil,3000,9,8
<-- 101 s345,skis nautiques,1800,13,8
<-- 101 f450,essai3,3,13,9
<-- 101 z400,léopard,500000,7,7
<-- 101 g457,panthère,800000,7,7
--> Requête : fin
--> Fermeture de la connexion à la base de données distante
<-- 200 Base fermée

10.4. Correspondências entre IDL e JAVA

Apresentamos aqui as correspondências entre os tipos simples IDL e JAVA:

tipo IDL
tipo Java
boolean
boolean
char
char
wchar
char
octet
byte
string
java.lang.String
wstring
java.lang.String
short
short
unsigned short
short
long
int
long sem sinal
int
long long
long
longo sem sinal
long
float
float
double
double

Vale lembrar que, para definir uma matriz de elementos do tipo T na interface IDL, utiliza-se a instrução:

    typedef   sequence<T> nomType;

e que, em seguida, utiliza-se nomType para referenciar o tipo da matriz. Assim, na interface do servidor SQL, utilizou-se a declaração:

    typedef sequence<string> resultats;

para que resultats indique uma matriz String[] em Java.