Skip to content

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

10.1. Introdução

No capítulo anterior, vimos como criar aplicações distribuídas em Java utilizando o pacote RMI. Aqui, abordamos o mesmo problema, desta vez utilizando a arquitetura CORBA. CORBA (Common Object Request Broker Architecture) é uma especificação definida pelo OMG (Object Management Group), que reúne muitas empresas do setor de TI. 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. A CORBA oferece a vantagem da interoperabilidade com aplicações escritas noutras linguagens.

10.2. O Processo de Desenvolvimento de uma Aplicação CORBA

10.2.1. Introdução

Para desenvolver uma aplicação cliente-servidor CORBA, seguiremos estes passos:

  1. Escrever a interface do servidor utilizando IDL (Interface Definition Language)
  2. Gerar as classes «skeleton» e «stub» do servidor
  3. Escrever o servidor
  4. Escrever o cliente
  5. Compilando todas as classes
  6. Iniciar um diretório de serviços CORBA
  7. iniciando o servidor
  8. iniciando o cliente

Utilizaremos o servidor echo, já utilizado no contexto RMI, como nosso primeiro exemplo. Isto permitirá ao leitor ver as diferenças entre os dois métodos.

A aplicação foi testada com o JDK 1.2.

10.2.2. Escrevendo a interface do servidor

Tal como no Java RMI, o servidor é definido pela sua interface em relação ao cliente. Enquanto as classes que implementam o servidor não são exigidas pelo cliente, as da sua interface são. Enquanto o Java RMI utilizava uma interface Java para gerar as classes «skeleton» e «stub» do servidor, a arquitetura Java CORBA requer que a interface seja descrita numa linguagem diferente do Java. Esta interface irá gerar várias classes, algumas das quais são utilizadas pelo cliente e outras pelo servidor.

A descrição da interface echo 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) da OMG. Para ser utilizável, deve ser analisada por um programa que irá gerar ficheiros de código-fonte na linguagem utilizada para desenvolver a aplicação CORBA. Aqui, iremos utilizar o programa idltojava.exe, que irá gerar os ficheiros de código-fonte .java necessários para a aplicação com base na interface anterior. O programa idltojava.exe não está incluído no JDK. Pode ser descarregado a partir do site da Sun em http://java.sun.com.

Vamos analisar algumas linhas da interface IDL anterior:

module echo
é equivalente ao **pacote** Java **echo**. A compilação da interface irá gerar o pacote Java *echo*, ou seja, um diretório contendo classes Java.
interface iSrvEcho
é equivalente à **interface** Java **iSrvEcho**. Irá gerar uma interface Java.
string echo(in string msg)
é equivalente à instrução Java **String echo(String msg)**. Os tipos na linguagem IDL não correspondem exatamente aos da linguagem Java. As correspondências são explicadas mais adiante neste capítulo. Na linguagem IDL, os parâmetros de uma função podem ser parâmetros de entrada (**in**), saída (**out**) ou entrada-saída (**inout**). Aqui, o método *echo* recebe um parâmetro de entrada *msg,* que é uma string, e devolve uma string como resultado.

A interface anterior é a do nosso servidor echo. 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. Compilando a interface IDL do servidor

Assim que a interface do servidor estiver definida, geramos 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 é utilizada para indicar que não deve ser utilizado nenhum pré-processador (mais comummente utilizada com C/C++). A compilação do ficheiro echo.idl cria um subdiretório echo contendo 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:

/*
 * File: ./ECHO/ISRVECHO.JAVA
 * From: ECHO.IDL
 * Date: Mon Mar 15 13:56:08 1999
 *   By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 1998 16:25:34
 */

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

Podemos ver que esta é quase uma tradução literal da interface IDL. Se tiver curiosidade suficiente para ver o conteúdo dos outros ficheiros .java, encontrará coisas mais complexas. Eis o que a documentação diz sobre o papel destes diferentes ficheiros:

iSrvEcho.java

a interface do servidor

_iSrvEchoImplbase.java

implementa a interface iSrvEcho anterior. É uma classe abstrata, o «esqueleto» do servidor, que fornece ao servidor a funcionalidade CORBA exigida pela aplicação distribuída.

_iSrvEchoStub.java

Este é o «stub» do servidor que o cliente irá utilizar. Fornece ao cliente a funcionalidade CORBA necessária para aceder ao servidor.

iSrvEchoHelper.java

Fornece os métodos necessários para gerir referências a objetos CORBA

iSrvEchoHolder.java

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

10.2.4. Compilar as classes geradas a partir da interface IDL

É aconselhável compilar as classes anteriores. Veremos noutro exemplo que os erros causados pelo funcionamento incorreto do gerador idltojava podem ser detetados aqui. Aqui, tudo corre bem e, após a compilação, os seguintes ficheiros estão presentes no diretório do pacote echo:

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. Implementação do servidor

10.2.5.1. Implementação da interface iSrvEcho

Definimos a interface iSrvEcho anteriormente. Vamos agora escrever a classe que implementa esta interface. Ela será derivada da classe _iSrvEchoImplbase.java, que, como referido acima, já implementa a interface iSrvEcho.

// imported packages
import echo.*;

// class implementing remote echo
public class srvEcho extends _iSrvEchoImplBase{
    // method performing the echo
    public String echo(String msg){
        return  "["  + msg + "]";
    }// fine echo
}// end of class

O código é autoexplicativo. Esta classe está guardada no ficheiro srvEcho.java no diretório pai do pacote da interface iSrvEcho.

Pode compilá-lo 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. Escrevendo a 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 ser acessível aos clientes. É este procedimento de registo que, ao nível do desenvolvimento, difere consoante se trate de uma aplicação CORBA ou RMI. Aqui está o procedimento para o servidor CORBA echo registado no ficheiro serverEcho.java:

// imported packages
 import echo.*
; import org.omg.CosNaming.
*; import org.omg.CosNaming.NamingContextPackage
.*; import org.omg.CORB

A.*; //----------- class serveu
rEcho public class serveu
    rEcho{ // ------- main: launches the ech
    o server // syntax pg machineAnnuaire portAnnuaire n
    omService // machine: machine supporting CORBA 
    directory // port: directory port
     CORBA // nomService: name of the service t

o  be registered public static void 
    main(String arg[]){ // are th
    e arguments there?
         if(arg.length!=3){ System.err.println("Syntaxe : pg machineAnnuaire por
        tAnnuaire nomSe
    r
    vice"); System.exit(1); }
     // retrieve the argu
    ments String machi
    ne=arg[0]; String port=arg[1]; String nomService=arg[2]; 

    try{

    // you need a CORBA object to work
    String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
      ORB orb=ORB.init(initORB,null);
    // put the service in the service directory
    // it will be called srvEcho
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
    // create the server and associate it with the srvEcho service
    srvEcho serveurEcho=new srvEcho();
      ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);
    // follow-up
    System.out.println("Serveur d'écho prêt");
    // waiting for customer requests
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
    // there has been an error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// hand
}// serveurEcho

A seguir, descrevemos os princípios básicos para iniciar o servidor, sem nos aprofundarmos em detalhes que podem parecer complexos à primeira vista. É importante recordar os pontos-chave do exemplo anterior, uma vez que estarão presentes em todos os servidores CORBA.

10.2.5.2.1. Parâmetros do servidor

Um servidor CORBA deve registar-se num serviço de diretório que opera numa determinada máquina e porta. A nossa aplicação receberá estas duas informações como parâmetros. O serviço registado deve ter um nome, que será o terceiro parâmetro.

10.2.5.2.2. Criação do objeto de acesso ao serviço de diretório CORBA

Para aceder ao serviço de diretório e registar o nosso servidor echo, precisamos de um objeto chamado 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 nulo. Se o primeiro parâmetro também tivesse sido definido como nulo, o par (máquina, porta) utilizado teria sido o padrão (localhost,900).

10.2.5.2.3. Registo do servidor no diretório de serviços CORBA

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

     // put the service in the service directory // 
    it will be called srvEcho
       org.omg.CORBA.Object o
b       jRef= orb.resolve_initial_references("
N     ameService"); NamingContext ncRef=NamingContextHe
l     per.narrow(objRef); NameComponent nc= new Nam
e     Component(nomService,""); 
     NameComponent path[]={nc}; // create the server a
    nd associate it with the srvEcho s
e         rvice srvEcho serveurEcho=new 
            srvEcho(); ncRef.rebind(path,serveurEcho); orb.connect(serveurEcho);

A primeira parte do código envolve a preparação do nome do serviço. Este nome é representado no código pela variável path. Um nome de serviço é composto por vários componentes:

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

Estes componentes do nome (NameComponent) são reunidos numa matriz, aqui path. É esta matriz que «nomeia» precisamente o serviço criado. Uma vez criado o nome, este permanece

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


srvEcho serveurEcho=new srvEcho();
  • e registá-lo no diretório

    ncRef.rebind(path,serveurEcho);
    orb.connect(serveurEcho);

10.2.5.3. Compilar a classe de inicialização do servidor

Compile 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. Gravação do cliente

10.2.6.1. O código

Estamos a escrever um cliente para testar o nosso serviço de eco. Passaremos os mesmos três parâmetros para o cliente que passámos para o servidor:

Máquina: a máquina onde se encontra o diretório do serviço CORBA

Porta: a porta na qual este diretório opera

serviceName: nome do serviço de eco

O cliente liga-se ao serviço de eco e, em seguida, solicita ao utilizador que digite mensagens no teclado. Estas mensagens são enviadas para o servidor de eco, que as reenvia. Um registo deste diálogo é apresentado no ecrã.

O cliente CORBA para o serviço echo é muito semelhante ao cliente RMI já escrito. Mais uma vez, o cliente deve ligar-se a um serviço de diretório para obter uma referência ao objeto servidor ao qual deseja ligar-se. A diferença entre os dois clientes reside apenas nisso. Aqui está o código para o cliente echo CORBA:


    

10.2.6.2. Ligar o cliente ao servidor

O cliente CORBA acima liga-se ao servidor utilizando a seguinte instrução:

        // on fait la liaison avec le serveur d'écho
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

Após esta operação, o cliente mantém uma referência ao servidor de eco. A partir deste ponto, um cliente CORBA não difere de um cliente RMI. O método privado que estabelece a ligação ao servidor é o seguinte:

// imported packages
import java.io.*;
import echo.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;

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

    public static void main(String arg[]){
         // syntax : cltEcho machineAnnuaire portAnnuaire nameservice
         // machine: machine where the CORBA service directory operates
         // port: port where the service directory operates
         // nomService: echo service name

         // argument verification
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaire portAnnuaire nomservice");
            System.exit(1);
        }

         // parameters are retrieved
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];

         // link to echo server
        iSrvEcho serveurEcho=getServeurEcho(machine,port,nomService);

         // client-server dialogue
        BufferedReader in=null;
        String msg=null;
        String reponse=null;
        iSrvEcho serveur=null;

        try{
             // open keyboard flow
            in=new BufferedReader(new InputStreamReader(System.in));
             // loop for reading msg to be sent to echo server
            System.out.print("Message : ");
            msg=in.readLine().toLowerCase().trim();
            while(! msg.equals("fin")){
                 // send msg to server and receive response
                reponse=serveurEcho.echo(msg);
                 // follow-up
                System.out.println("Réponse serveur : " + reponse);
                 // next msg
                System.out.print("Message : ");                
                msg=in.readLine().toLowerCase().trim();
            }// while
             // it's over
            System.exit(0);
         // error management         
        } catch (Exception e){
            System.err.println("Erreur : " + e);
            System.exit(2);
        }// try
    }// hand

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

         // requests an echo server reference
         // follow-up
        System.out.println("--> Connexion au serveur CORBA en cours...");
         // echo server reference
        iSrvEcho serveurEcho=null;
        try{
             // we request a CORBA object to work with
            String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};    
          ORB orb=ORB.init(initORB,null);
             // use the directory service to locate the echo server
          org.omg.CORBA.Object objRef=
                orb.resolve_initial_references("NameService");
          NamingContext ncRef=NamingContextHelper.narrow(objRef);
             // the service required is called srvEcho - it is requested
          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
         // return the reference to the server
        return serveurEcho;
    }// getServeurEcho

}// class

Vemos 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 echo

        org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
  • Solicitamos uma referência ao serviço echo ao serviço de diretório (é aqui que diferimos do servidor)

serveurEcho=iSrvEchoHelper.narrow(ncRef.resolve(path));

10.2.6.3. Compilação

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. Iniciando o serviço de diretório

Numa máquina 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 inicia o serviço de diretório na porta 1000 do computador.

O serviço de diretório tnameserv apresenta um ecrã semelhante ao seguinte:

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

É difícil de ler, mas repare na última linha: o serviço está ativo na porta 1000.

10.2.7.2. Iniciar o servidor echo

O serviço echo é 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. Iniciando o cliente na mesma máquina 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. Iniciar o cliente numa máquina Windows que não seja o 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

Aqui, revisamos o código do servidor SQL anteriormente analisado no contexto do Java RMI, mais uma vez para destacar as semelhanças e diferenças entre as duas abordagens. Como lembrete, a função deste servidor SQL é a seguinte: ele é executado numa máquina Windows e permite que clientes remotos acedam às bases de dados ODBC públicas nessa máquina Windows.

Image

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

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

O servidor executa as consultas SQL do cliente e envia os resultados de volta ao cliente. Esta é a sua função principal, razão pela qual o chamamos de servidor SQL. Aplicamos os vários passos anteriormente abordados com o servidor echo.

10.3.2. Escrever a interface IDL do servidor

Como lembrete, eis a interface RMI que utilizámos para o servidor:

import java.rmi.*;

 // remote interface p
ublic interface interSQL extends Remote{ 
     public String connect(String pilote, String url, String id, String mdp
        ) throws java.rmi.RemoteExcept
    ion; public String[] executeSQL(String requete, String separa
        teur) throws java.rmi.RemoteE
    xception; public Str
        ing close() throws java.rmi.Re
moteException; }

As funções dos vários métodos eram as seguintes:

Connect: o cliente liga-se a uma base de dados remota, fornecendo o controlador, o URL JDBC, bem como o seu ID e palavra-passe para aceder à base de dados. O servidor devolve uma cadeia de caracteres indicando 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. Especifica o caractere que deve separar os campos nos resultados que lhe são devolvidos. O servidor devolve uma matriz de cadeias de caracteres:

    100 n
para uma consulta de atualização da base de dados, em que 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 devolveu resultados
    101 ligne1
    101 ligne2
    101 ...
se a consulta devolveu resultados. As linhas devolvidas pelo servidor são as linhas de resultados da consulta.

Fechar: 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
};// module

A única novidade em relação ao que vimos na interface IDL do servidor Echo é a utilização da palavra-chave sequence. Esta palavra-chave permite definir uma matriz unidimensional. A definição é feita em duas etapas:

  • definir um tipo para designar a matriz, neste caso results:
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>, ou seja, uma matriz dinâmica (sem tamanho fixo) de cadeias de caracteres.

  • utilizando o novo tipo onde necessário
resultats executeSQL(in string requete, in string separateur);

O método executeSQL devolve, portanto, uma matriz de cadeias de caracteres.

10.3.3. Compilando a 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

Podemos ver que a compilação criou um diretório com o nome do módulo de interface IDL (srvSQL). Vamos ver 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

Note-se que os ficheiros Helper e Holder são classes associadas aos parâmetros de entrada/saída e aos 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 de resultados criado na interface IDL.

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

/*
 * File: ./SRVSQL/INTERSQL.JAVA
 * From: SRVSQL.IDL
 * Date: Fri Mar 19 09:59:48 1999
 *   By: D:\JAVAIDL\IDLTOJ~1.EXE Java IDL 1.2 Aug 18 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()
;
}

Podemos ver que temos a mesma interface que a utilizada para o cliente-servidor RMI. Portanto, podemos 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. Escrevendo no SQL Server

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. Para além desta 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:

// imported packages
import java.sql.*;
import java.util.*;
import srvSQL.*;

// class SQLServant
public class SQLServant extends _interSQLImplBase{

    // global class data
    private Connection DB;

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

        // connection to url database via driver
        // identification with identity id and password mdp

        String resultat=null;            // result of the method
        try{
            // loading the driver
            Class.forName(pilote);
            // connection request
            DB=DriverManager.getConnection(url,id,mdp);
            // ok
            resultat="200 Connexion réussie";
        } catch (Exception e){
            // error
            resultat="500 Echec de la connexion (" + e + ")";
        }
        // end
        return resultat;
    }            

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

        // executes a SQL query on the DB database
        // and puts the results in an array of strings

        // data required to execute the request
        Statement S=null;
        ResultSet RS=null;
        String[] lignes=null;
        Vector resultats=new Vector();
        String ligne=null;

        try{
            // create query container
            S=DB.createStatement();
            // request execution
            if (! S.execute(requete)){
                // update request
                // returns the number of lines updated
                lignes=new String[1];
                lignes[0]="100 "+S.getUpdateCount();
                return lignes;
            }
            // it was a query request
            // retrieve results
            RS=S.getResultSet();
            // number of Resultset fields
            int nbChamps=RS.getMetaData().getColumnCount();
            // we exploit them
            while(RS.next()){
                // create results line
                ligne="101 ";
                for (int i=1;i<nbChamps;i++)
                    ligne+=RS.getString(i)+separateur;
                ligne+=RS.getString(nbChamps);
                // add to results vector
                resultats.addElement(ligne);
            }// while
            // end of results processing
            // free up resources
            RS.close();
            S.close();
            // we return the results
            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){
            // error
            lignes=new String[1];
            lignes[0]="500 " + e;
            return lignes;
        }// try-catch
    }// executeSQL

    // --------------- close
    public String close(){
        // closes database connection
        String resultat=null;
        try{
            DB.close();
            resultat="200 Base fermée";
        } catch (Exception e){
            resultat="500 Erreur à la fermeture de la base ("+e+")";
        }
        // return result
        return resultat;
    }
}// class SQLServant

Esta classe encontra-se 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. Escrever o programa de inicialização do SQL Server

A classe anterior representa o SQL Server depois de ter sido iniciado. Antes disso, ele deve ser registado num diretório de serviços CORBA. Tal como no serviço echo, faremos isso utilizando uma classe especial à qual passaremos três parâmetros em tempo de execução:

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

Porta: a porta na qual este diretório opera

serviceName: nome do serviço SQL

O código desta classe é quase idêntico ao da classe que executava a mesma tarefa para o serviço echo. Destacámos em negrito a linha que difere entre as duas classes: não cria o mesmo objeto servidor. Podemos ver, portanto, que o mecanismo de inicialização do servidor permanece o mesmo. Se isolarmos este mecanismo numa classe, como foi feito aqui, torna-se praticamente transparente para o programador.

// imported packages
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
import org.omg.CORBA.*;

//----------- class serveurSQL
public class serveurSQL{
    // ------- main: launches the SQL server
  public static void main(String arg[]){
        // serveurSQL machine port service

        //do we have the right number of arguments
        if(arg.length!=3){
            System.err.println("Syntaxe : pg machineAnnuaireCorba portAnnuaireCorba nomService");
            System.exit(1);
        }
        // retrieve the arguments
        String machine=arg[0];
        String port=arg[1];
        String nomService=arg[2];
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
    try{
            // you need a CORBA object to work
      ORB orb=ORB.init(initORB,null);
            // put the service in the service directory
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
      NameComponent nc= new NameComponent(nomService,"");
      NameComponent path[]={nc};
        // create the server and associate it with the srvSQL service
        SQLServant serveurSQL=new SQLServant();
      ncRef.rebind(path,serveurSQL);
        orb.connect(serveurSQL);
        // follow-up
        System.out.println("Serveur SQL prêt");
        // waiting for customer requests
      java.lang.Object sync=new java.lang.Object();
      synchronized(sync){
        sync.wait();
      }
    } catch(Exception e){
            // there has been an error
      System.err.println("Erreur " + e);
      e.printStackTrace(System.err);
    }
  }// hand
}// 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. Código 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: a porta na qual este diretório opera

nome do serviço: nome do serviço SQL

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

baseUrl: URL JDBC da base de dados a gerir

id: ID do cliente ou nulo se não houver ID

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

separator: caractere que o servidor SQL deve utilizar para separar os campos nas linhas de resultado 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 o diretório de serviços CORBA está localizado

porta: a porta na qual este diretório opera

srvSQL: srvSQL, o nome CORBA do servidor SQL

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

urlBase: jdbc:odbc:articles, para utilizar uma base de dados articles declarada na lista de bases de dados ODBC públicas na máquina Windows

id: null, sem ID

password: null, sem palavra-passe

separador: , os campos do resultado serão separados por uma vírgula

Uma vez iniciado com os parâmetros acima, o cliente executa os seguintes passos:

  • liga-se à máquina machine na porta port para solicitar o serviço CORBA srvSQL
  • solicita uma ligação à base de dados de artigos
connect(‘’sun.jdbc.odbc.JdbcOdbcDriver’’, ‘‘jdbc:odbc:articles’’, ’’’’, ’’’’)
  • pede ao utilizador para digitar uma consulta SQL no teclado
  • envia-a para o servidor SQL
executeSQL(requete, ’’,’’);
  • Exibe os resultados devolvidos pelo servidor no ecrã
  • solicita novamente ao utilizador que introduza uma consulta SQL no teclado. Parará quando a consulta estiver concluída.

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

  • o código é idêntico ao do cliente RMI já estudado. A diferença reside no processo de solicitação do serviço ao diretório, um processo isolado no método getServeurSQL.
  • o método getServeurSQL é idêntico ao escrito para o cliente echo

Podemos, portanto, ver que:

  • Um cliente CORBA difere de um cliente RMI apenas na forma como contacta o servidor
  • este método é idêntico para todos os clientes CORBA
import srvSQL.*;
import org.omg.CosNaming.*;
import org.omg.CORBA.*;
import java.io.*;

public class clientSQL {

    // global class data
    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[]){
        // syntax : cltSQL machine port separator driver url id mdp
        // machine port: machine & service directory port CORBA to contact
        // department: department name
        // driver: driver to be used for the database to be processed
        // urlBase: jdbc url of the database to be used
        // id: user identity
        // mdp: password
        // separator: string separating fields in query results

        // check number of arguments
        if(arg.length!=8)
            erreur(syntaxe,1);

        // init database connection parameters
        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];        

        // directory service parameters CORBA
        String[] initORB={"-ORBInitialHost",arg[0],"-ORBInitialPort",arg[1]};
        // client CORBA - request a server reference SQL
        interSQL serveurSQL=getServeurSQL(machine,port,service);

        // client-server dialogue
        String requete=null;
        String reponse=null;
        String[] lignes=null;
        String codeErreur=null;

        try{
            // open keyboard flow
            in=new BufferedReader(new InputStreamReader(System.in));
            // follow-up
            System.out.println("--> Connexion à la base de données en cours");
            // initial database connection request
            reponse=serveurSQL.connect(pilote,urlBase,id,mdp);
            // follow-up
            System.out.println("<-- "+reponse);
            // response analysis
            codeErreur=reponse.substring(0,3);
            if(codeErreur.equals("500")) 
                erreur("Abandon sur erreur de connexion à la base",3);
            // loop for reading requests to be sent to the server SQL
            System.out.print("--> Requête : ");
            requete=in.readLine().toLowerCase().trim();
            while(! requete.equals("fin")){
                // send request to server and receive response
                lignes=serveurSQL.executeSQL(requete,separateur);
                // follow-up
                afficheLignes(lignes);
                // following request
                System.out.print("--> Requête : ");                
                requete=in.readLine().toLowerCase().trim();
            }// while
            // follow-up
            System.out.println("--> Fermeture de la connexion à la base de données distante");
            // close the connection
            reponse=serveurSQL.close();
            // follow-up
            System.out.println("<-- " + reponse);
            // end
            System.exit(0);
        // error management        
        } catch (Exception e){
            erreur("Abandon sur erreur : " + e,2);
        }// try
    }// hand

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

    // ------------ error
    private static void erreur(String msg, int exitCode){
        // error msg display
        System.err.println(msg);
        // possible release of resources
        try{
            in.close();
            serveurSQL.close();
        } catch(Exception e){}
        // we leave
        System.exit(exitCode);
    }// error

    // ---------------------- getServeurSQL
    private static interSQL getServeurSQL(String machine, String port, String service){
        // requests a server reference SQL
        // machine: service directory machine CORBA
        // port: service directory port CORBA
        // service: service name CORBA to request

        // follow-up
        System.out.println("--> Connexion au serveur CORBA en cours...");
        // server reference SQL
        interSQL serveurSQL=null;
        // directory service parameters CORBA
        String[] initORB={"-ORBInitialHost",machine,"-ORBInitialPort",port};
                try{
            // a CORBA object is required to work - to do this, we pass the port 
            // service directory listening CORBA
      ORB orb=ORB.init(initORB,null);
            // use the directory service to locate the SQL server
      org.omg.CORBA.Object objRef=
        orb.resolve_initial_references("NameService");
      NamingContext ncRef=NamingContextHelper.narrow(objRef);
            // the service required is called srvSQL - it is requested
      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
        // return the reference to the server
        return serveurSQL;
    }// getServeurSQL

}// class

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é-requisitos

Partimos do princípio de que existe uma base de dados ACCESS chamada Articles disponível publicamente na máquina Windows do servidor SQL:

Image

Esta base de dados tem a seguinte estrutura:

nome
tipo
código
código de artigo de 4 caracteres
nome
o seu nome (cadeia de caracteres)
preço
o seu preço (real)
stock_atual
stock atual (inteiro)
stock_mínimo
o stock mínimo (número inteiro) abaixo do qual o artigo deve ser reabastecido

10.3.7.2. Iniciar o 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. A iniciar o servidor SQL

A iniciar o servidor SQL:

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

Aparece o seguinte numa janela do DOS:

    Serveur SQL prêt

10.3.7.4. Iniciar 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 ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 sperm whale,6,6
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Requête : delete from articles where stock_mini<7
<-- 100 1
--> Requête : select nom, stock_actu, stock_mini from articles
<-- 101 bicycle,31,8
<-- 101 arc,9,8
<-- 101 canoeing,7,7
<-- 101 rifle,9,8
<-- 101 water skis,13.8
<-- 101 test3,13,9
<-- 101 leopard,7,7
<-- 101 panther,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base

10.3.7.5. Iniciar um cliente numa máquina que não seja o servidor

Aqui estão os resultados obtidos com um cliente numa máquina diferente 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 ,
--> Connection to server CORBA in progress...
--> Current database connection
<-- 200 Successful connection
--> Requête : select * from articles
<-- 101 a300,v_lo,1202,31,8
<-- 101 d600,arc,5000,9,8
<-- 101 d800,canoe,1502,7,7
<-- 101 x123,rifle,3000,9,8
<-- 101 s345,water skis,1800,13,8
<-- 101 f450,test3,3,13,9
<-- 101 z400,leopard,500000,7,7
<-- 101 g457,panther,800000,7,7
--> Query: end
--> Closing the remote database connection
<-- 200 Closed base

10.4. Correspondências IDL - JAVA

Aqui apresentamos as correspondências entre tipos simples de IDL e Java:

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

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

    typedef   sequence<T> nomType;

e que, em seguida, usamos TypeName para nos referirmos ao tipo da matriz. Assim, na interface do SQL Server, utilizámos a declaração:

    typedef sequence<string> resultats;

para que results se refira a uma matriz String[] em Java.