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:

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:
- escrita da interface do servidor com IDL (Interface Definition Language)
- geração das classes «esqueleto» e «stub» do servidor
- programação do servidor
- criação do cliente
- compilação de todas as classes
- lançamento de um diretório de serviços CORBA
- inicialização do servidor
- 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:
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:
é 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.
é equivalente à interface iSrvEcho do Java. Dará origem a uma interface Java.
é 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:
a interface do servidor
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.
É a imagem («stub») do servidor que será utilizada pelo cliente. Fornece ao cliente as funcionalidades CORBA para aceder ao servidor.
Fornece os métodos necessários para a gestão das referências de objetos CORBA
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:
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
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)
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:
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:
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.

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:
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:
para uma consulta de atualização da base de dados, sendo n o número de linhas atualizadas
se a consulta tiver gerado um erro
se a consulta não tiver gerado nenhum resultado
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:
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»:
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
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:
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:
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
- solicita ao utilizador que introduza uma consulta SQL no teclado
- envia-a para o servidor SQL
- 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:

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:
É exibido numa janela DOS:
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:
e que, em seguida, se utiliza nomType para referenciar o tipo da matriz. Assim, na interface do servidor SQL, utilizou-se a declaração:
para que resultats designe uma matriz String[] em Java.