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:

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:
- Escrever a interface do servidor utilizando IDL (Interface Definition Language)
- Gerar as classes «skeleton» e «stub» do servidor
- Escrever o servidor
- Escrever o cliente
- Compilando todas as classes
- Iniciar um diretório de serviços CORBA
- iniciando o servidor
- 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:
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:
é equivalente ao **pacote** Java **echo**. A compilação da interface irá gerar o pacote Java *echo*, ou seja, um diretório contendo classes Java.
é equivalente à **interface** Java **iSrvEcho**. Irá gerar uma interface Java.
é 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:
a interface do servidor
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.
Este é o «stub» do servidor que o cliente irá utilizar. Fornece ao cliente a funcionalidade CORBA necessária para aceder ao servidor.
Fornece os métodos necessários para gerir referências a objetos CORBA
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:
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
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)
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:
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:
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.

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:
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:
para uma consulta de atualização da base de dados, em que n é o número de linhas atualizadas
se a consulta gerou um erro
se a consulta não devolveu resultados
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:
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:
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
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:
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:
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
- pede ao utilizador para digitar uma consulta SQL no teclado
- envia-a para o servidor SQL
- 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:

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:
Aparece o seguinte numa janela do DOS:
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:
e que, em seguida, usamos TypeName para nos referirmos ao tipo da matriz. Assim, na interface do SQL Server, utilizámos a declaração:
para que results se refira a uma matriz String[] em Java.