Skip to content

10. Construção de aplicações distribuídas CORBA

10.1. Introdução

No capítulo anterior, vimos como criar aplicações distribuídas em Java com o pacote RMI. Abordamos aqui o mesmo problema, desta vez com a arquitetura CORBA. A CORBA (Common Object Request Broker Architecture) é uma especificação definida pelo OMG (Object Management Group), que reúne numerosas empresas do setor informático. A CORBA define um «barramento de software» acessível a aplicações escritas em diferentes linguagens:

Image

Veremos que a construção de uma aplicação distribuída com CORBA é semelhante ao método utilizado com Java RMI: os conceitos são semelhantes. O CORBA apresenta a vantagem da interoperabilidade com aplicações escritas noutras 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 os seguintes passos:

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

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

A aplicação foi testada com o jdk1.2.

10.2.2. Criação da interface do servidor

Tal como no Java RMI, do ponto de vista do cliente, o servidor é definido pela sua interface. Se as classes que implementam o servidor não forem necessárias para o cliente, as da sua interface são-no. Enquanto o Java RMI utilizava uma interface Java que dava origem às classes «esqueleto» e «stub» do servidor, a arquitetura Java CORBA requer a descrição da interface numa linguagem diferente do Java. Esta interface dará origem a várias classes, algumas das quais 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 num ficheiro echo.idl. Está escrita na linguagem IDL (Interface Definition Language) do OMG. Para ser utilizável, deve ser analisada por um programa que irá criar ficheiros-fonte na linguagem utilizada para desenvolver a aplicação CORBA. Neste caso, utilizaremos o programa idltojava.exe, que, a partir da interface anterior, criará os ficheiros fonte .java necessários para a aplicação. O programa idltojava.exe não é fornecido com o JDK. 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 dará origem ao pacote Java echo c.a.d, um diretório que contém classes Java.

interface iSrvEcho

é equivalente à interface iSrvEcho do Java. Dará origem a 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 mais adiante neste capítulo. Na linguagem IDL, os parâmetros de uma função podem ser parâmetros 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 cadeia de caracteres, e devolve uma cadeia de caracteres como resultado.

A interface anterior é a do nosso servidor de eco. Recorde-se 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

Depois de definida a interface do servidor, geram-se os ficheiros 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 é necessário utilizar um pré-processador (utilizado principalmente com C/C++). A compilação do ficheiro echo.idl gera um subdiretório echo que contém os seguintes ficheiros:

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 ficheiro iSrvEcho.java é o ficheiro Java que descreve a interface do servidor:

/*  * Ficheiro: ./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)
;
}

Vê-se que se trata praticamente de uma tradução palavra por palavra da interface IDL. Se tivermos curiosidade em consultar o conteúdo dos outros ficheiros .java, encontraremos elementos mais complexos. Eis o que diz a documentação sobre a função destes diferentes ficheiros:

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 à aplicação distribuída.

_iSrvEchoStub.java

É a imagem («stub») do servidor que será utilizada pelo cliente. Fornece ao cliente as funcionalidades CORBA para aceder ao servidor.

iSrvEchoHelper.java

Fornece os métodos necessários para a gestão das referências de objetos CORBA

iSrvEchoHolder.java

Fornece os métodos necessários para a gestão 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

É aconselhável compilar as classes anteriores. Veremos noutro exemplo que, neste caso, é possível detetar erros decorrentes de um funcionamento incorreto do gerador idltojava. Aqui, tudo corre bem e, após a compilação, temos no diretório do pacote echo os seguintes ficheiros:

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. Registo do servidor

10.2.5.1. Implementação da interface iSrvEcho

Definimos anteriormente a interface iSrvEcho. Vamos agora escrever a classe que implementa esta interface. Esta será derivada da classe _iSrvEchoImplbase.java que, tal como 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 implementa o eco
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fim do eco
}// fim da classe

O código é autoexplicativo. Esta classe está registada no ficheiro 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. Registo da classe de criação do servidor

Tal como acontece com uma aplicação cliente-servidor RMI, um servidor CORBA deve ser registado num diretório para que os clientes possam aceder-lhe. É este procedimento de registo que, ao nível do desenvolvimento, difere consoante se trate de uma aplicação CORBA ou RMI. Eis o código do servidor CORBA de eco registado no ficheiro 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 registar

  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
    // chamar-se-á 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 associamo-lo ao serviço srvEcho
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // acompanhamento
    System.out.println("Serveur d'écho prêt");
    // aguarda pedidos 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);
    }
  }// principal
}// serveurEcho

A seguir, explicamos as linhas gerais do arranque do servidor, sem entrar em pormenores que, à primeira vista, podem parecer complexos. É importante reter as linhas gerais do exemplo anterior, que se repetem em qualquer servidor CORBA.

10.2.5.2.1. Os parâmetros do servidor

Um servidor CORBA deve registar-se num serviço de diretório que opere numa máquina e numa porta específicas. A nossa aplicação receberá estes dois dados como parâmetros. O serviço assim registado 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 aceder ao serviço de diretório e registar o nosso servidor de eco, precisamos de um objeto denominado ORB (Object Request Broker), obtido através do 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. Registar o servidor no diretório de serviços CORBA

O registo do servidor no diretório é efetuado através das seguintes operações:

    // o serviço é adicionado ao diretório de serviços
    // chamar-se-á 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 associamo-lo 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. Este 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 um tipo NamingContext, neste caso ncRef.
  • o nome do serviço, neste caso nomService, que foi passado como parâmetro ao servidor

Estes componentes do nome (NameComponent) são reunidos numa tabela, neste caso path. É esta tabela 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();
  • e registá-la no diretório
    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilação da classe de lançamento 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 a escrever um cliente para testar o nosso serviço de eco. Passaremos ao cliente os mesmos três parâmetros que passámos ao servidor:

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

Porta: porta em que este diretório opera

nomService: nome do serviço de eco

O cliente liga-se ao serviço de eco e, em seguida, pede ao utilizador para digitar mensagens no teclado. Estas são enviadas para o servidor de eco, que as reenvia. Este diálogo é acompanhado no ecrã.

O cliente CORBA do serviço de eco é muito semelhante ao cliente RMI já escrito. Mais uma vez, o cliente tem de se ligar a um serviço de diretório para obter uma referência do objeto-servidor ao qual pretende ligar-se. A diferença entre os dois clientes reside aí e apenas aí. Eis o código do cliente de eco CORBA:


    

10.2.6.2. A ligação do cliente ao servidor

O cliente CORBA acima liga-se ao servidor através da instrução:

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

No final desta operação, o cliente obtém uma referência do servidor «écho». Posteriormente, um cliente CORBA não difere de um cliente RMI. O método privado que assegura a ligação ao 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 onde funciona o diretório de serviços CORBA
         // porta: porta em que o diretório de serviços está em funcionamento
         // 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);
        }

         // recuperam-se os parâmetros
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

         // estabelece-se a ligaçã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 enviar para o servidor de eco
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                 // envio da mensagem para o servidor e receção da resposta
                reponse=serveurEcho.echo(msg);
                 // acompanhamento
                System.out.println("Réponse serveur : " + reponse);
                 // mensagem seguinte
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
             // terminado
            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
         // acompanhamento
        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 chama-se srvEcho — está a ser 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
         // devolve-se 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á contactar 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};
  • solicita-se ao serviço de diretório uma referência do serviço de eco (é aqui 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. Início do serviço de diretório

Num computador com Windows, iniciamos o serviço de diretório da seguinte forma:

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 apresenta uma mensagem no ecrã semelhante à seguinte:

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

Não é muito legível, mas vamos ficar com a ú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 apresenta:

    Serveur d’écho prêt

10.2.7.3. Inicialização do cliente no mesmo computador que o do 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 num 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, com o objetivo de destacar os pontos em comum entre os dois métodos, bem como as suas diferenças. Recordamos a função deste servidor SQL: encontra-se numa máquina Windows e permite que os clientes remotos acedam às bases de dados públicas ODBC deste computador Windows.

Image

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

  • ligar-se à base de dados da sua escolha
  • enviar pedidos SQL
  • encerrar a ligação

O servidor executa as consultas SQL do cliente e envia-lhe os resultados. Esta é a sua função essencial e é por isso que lhe chamamos servidor SQL. Aplicamos os diferentes passos vistos anteriormente com o servidor de eco.

10.3.2. Criação da interface IDL do servidor

Recorde-se, para memória, a interface RMI que utilizámos 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 liga-se a uma base de dados remota, indicando o controlador, o URL JDBC, bem como a sua identificação (id) e palavra-passe (mdp) para aceder a essa base de dados. O servidor devolve-lhe uma cadeia de caracteres que indica o resultado da ligação:

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

executeSQL: o cliente solicita a execução de uma consulta SQL na base de dados à qual está ligado. Indica o carácter que deve separar os campos nos resultados que lhe são devolvidos. O servidor devolve um tabuleiro de cadeias de caracteres:

    100 n

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

    500 msg d’erreur

se a consulta tiver gerado um erro

    501 Pas de résultats

se a consulta não tiver gerado nenhum resultado

    101 ligne1
    101 ligne2
    101 ...

se a consulta tiver gerado resultados. As linhas assim devolvidas pelo servidor são as linhas de resultados da consulta.

Close: o cliente encerra a sua ligação à base de dados remota. O servidor devolve uma cadeia de caracteres que indica o resultado deste 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 com a interface IDL do servidor de eco é a utilização da palavra-chave «sequence». Esta palavra-chave permite definir um tabuleiro 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 dos programadores de C/C++: permite definir um novo tipo. Aqui, o tipo resultats é definido como equivalente ao tipo sequence<string>, c.a.d, ou seja, 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 devolve, portanto, um array de cadeias de caracteres.

10.3.3. Compilação da interface IDL do servidor

A interface IDL anterior é colocada no ficheiro srvSQL.idl. Compilamos este ficheiro:

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

Verifica-se que a compilação deu origem a um diretório com o nome do módulo da interface IDL (srvSQL). Vejamos o conteúdo deste diretório:

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

Recorde-se que os ficheiros Helper e Holder são classes relacionadas com os parâmetros de entrada e saída e os resultados dos métodos da interface remota. O diretório srvSQL contém todos os ficheiros .java relacionados com a interface interSQL definida no ficheiro .idl. Contém também ficheiros relacionados com o tipo resultats criado na interface IDL.

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

/*  * Ficheiro: ./SRVSQL/INTERSQL.JAVA  * De: SRVSQL.IDL  * Data: Sexta-feira, 19 de março, 09:59:48, 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()
;
}

Verifica-se que temos a mesma interface que a utilizada para o cliente-servidor RMI. Podemos, portanto, continuar. Vamos compilar todos estes ficheiros .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

Vamos agora escrever o código do servidor SQL. Recorde-se que esta classe deve derivar da classe abstrata _nomInterfaceImplBase, gerada pela compilação do ficheiro IDL. À parte esta particularidade e excluindo as sequências de código relacionadas com o registo do serviço num 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;

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

        // ligação à base de dados url através do controlador
        // identificação com o ID e a palavra-passe

        String resultat=null;            // resultado do método
        try{
            // carregamento do controlador
            Class.forName(pilote);
            // pedido de ligaçã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 na base de dados DB
        // e coloca os resultados numa tabela de cadeias

        // 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 contentor da consulta
            S=DB.createStatement();
            // execução da consulta
            if (! S.execute(requete)){
                // consulta de atualização
                // é devolvido o número de linhas atualizadas
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // era uma consulta
            // recuperam-se os resultados
            RS=S.getResultSet();
            // número de campos do Resultset
            int nbChamps=RS.getMetaData().getColumnCount();
            // processa-se os resultados
            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();
            // retornam-se os 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(){
        // encerra a ligação à base 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

Esta classe está incluída no ficheiro SQLServant.java que estamos a compilar:

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 arranque do servidor SQL

A classe anterior representa o servidor SQL uma vez iniciado. Antes disso, este deve ser registado num diretório de serviços CORBA. Tal como no caso do serviço de eco, faremos isso com 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 em que este diretório opera

nomService: nome do serviço SQL

O código desta classe é praticamente idêntico ao da classe que fazia o mesmo para o serviço de eco. Destacámos em negrito a linha que difere entre as duas classes: ela não cria o mesmo objeto-servidor. Vemos, portanto, que continuamos a ter o mesmo mecanismo de arranque do servidor. Se isolarmos esse mecanismo numa classe, como foi feito aqui, ele torna-se praticamente transparente para o programador.

// 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

        //temos o número correto de argumentos
        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{
            // é necessário 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 associamo-lo ao serviço srvSQL
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // acompanhamento
        System.out.println("Serveur SQL prêt");
        // aguarda pedidos 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 esta 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. Escrita 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 se encontra o diretório de serviços CORBA

porta: porta em que este diretório opera

nomService: nome do serviço SQL

driver: driver que o servidor SQL deve utilizar para gerir a base de dados pretendida

urlBase: URL JDBC da base de dados a gerir

id: identificação do cliente ou «null» se não houver identificação

mdp: palavra-passe do cliente ou nulo se não houver palavra-passe

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

Eis 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 se encontra o diretório de serviços CORBA

porta: porta em que este diretório opera

srvSQL: srvSQL, nome CORBA do servidor SQL

controlador: sun.jdbc.odbc.JdbcOdbcDriver, o controlador habitual para bases de dados com interface ODBC

urlBase: jdbc:odbc:articles, para utilizar uma base de dados «articles» declarada na lista de bases de dados públicas ODBC do computador Windows

id: null, sem identificação

mdp: null, sem palavra-passe

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

Depois de iniciado com os parâmetros anteriores, o cliente segue os seguintes passos:

  • liga-se ao computador «machine» na porta «port» para solicitar o serviço CORBA srvSQL
  • solicita a ligação à base de dados de artigos
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • solicita ao utilizador que introduza uma consulta SQL no teclado
  • envia-a para o servidor SQL
executeSQL(requete, ’’,’’);
  • exibe no ecrã os resultados devolvidos pelo servidor
  • solicita novamente ao utilizador que introduza uma consulta SQL através do teclado. O programa terminará quando a consulta terminar.

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

  • o código é idêntico ao do cliente RMI já analisado. Difere deste no processo de solicitação do serviço ao diretório, processo isolado no método getServeurSQL.
  • O método getServeurSQL é idêntico ao que foi escrito para o cliente de eco

Vemos, portanto, que:

  • um cliente CORBA difere de um cliente RMI apenas pela forma como contacta o 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 controlador URL ID palavra-passe
        // máquina porta: máquina e porta do diretório de serviços CORBA a contactar
        // serviço: nome do serviço
        // driver: driver a utilizar para a base de dados a explorar
        // urlBase: URL JDBC da base de dados a utilizar
        // id: identificação do utilizador
        // mdp: a sua palavra-passe
        // separador: cadeia de caracteres 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 ligação à base 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 - é solicitada 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));
            // acompanhamento
            System.out.println("--> Connexion à la base de données en cours");
            // pedido de ligação inicial à base 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 enviar ao servidor SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // envio da solicitação ao servidor e receção 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 ligação
            reponse=serveurSQL.close();
            // seguimento
            System.out.println("<-- " + reponse);
            // fim
            System.exit(0);
        // gestão de erros        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// tentativa
    }// 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);
        // eventual libertação de recursos
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // sair
        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 solicitar

        // 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 tal, 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 chama-se srvSQL — solicitamo-lo
      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
        // devolve-se 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 uma base de dados ACCESS denominada «Artigos» está disponível publicamente na máquina Windows do servidor SQL:

Image

Esta base de dados tem a seguinte estrutura:

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

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. Início do servidor SQL

Iniciamos o servidor SQL:

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

É exibido numa janela DOS:

    Serveur SQL prêt

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

Eis 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: selecionar 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 numa máquina diferente da do servidor

Eis os resultados obtidos com um cliente numa máquina diferente daquela em que se encontra o 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 IDL - 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

Recorde-se que, para definir um tabuleiro de elementos do tipo T na interface IDL, utiliza-se a instrução:

    typedef   sequence<T> nomType;

e que, em seguida, se utiliza 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 designe uma matriz String[] em Java.