Introdução ao desenvolvimento de aplicações CORBA em Java

Propaganda
Departamento de Engenharia Electrotécnica
REDES INTEGRADAS DE TELECOMUNICAÇÕES II
2012 / 2013
Mestrado Integrado em Engenharia Electrotécnica
e de Computadores
4º ano
8º semestre
Introdução ao
desenvolvimento de aplicações CORBA em Java
http://tele1.dee.fct.unl.pt
Luis Bernardo / Pedro Amaral
ÍNDICE
Índice ......................................................................................................................................................................... 1 1. Objectivo ............................................................................................................................................................... 2 2. A arquitectura CORBA ......................................................................................................................................... 2 3. Construção de aplicações CORBA ........................................................................................................................ 3 3.1. Definição de interfaces (IDL) ......................................................................................................................... 4 3.2. Clientes (invocação de funções) ..................................................................................................................... 8 3.2.1. Referências para objectos ........................................................................................................................ 9 3.2.2. Realização de um cliente ......................................................................................................................... 9 3.3. Servidores (recepção de invocações) ............................................................................................................ 10 3.3.1. Realização de um servidor ..................................................................................................................... 10 3.3.2. Geração de excepções de utilizador....................................................................................................... 13 3.3.3. Modos de activação de objectos no CORBA ........................................................................................ 13 3.3.4. Integração do CORBA no Java ............................................................................................................. 13 3.4. Passagem de parâmetros em funções IDL .................................................................................................... 15 3.5. Serviço de Nomes ......................................................................................................................................... 16 3.6. Serviço de Eventos ....................................................................................................................................... 18 4. OpenORB ............................................................................................................................................................ 21 5. Exemplos de aplicações ....................................................................................................................................... 22 5.1. Aplicação cliente-servidor utilizando o serviço de nomes ........................................................................... 22 5.1.1. Cliente ........................................................................................................................................................ 22 5.1.2. Servidor ..................................................................................................................................................... 23 5.1.3. Exercício .................................................................................................................................................... 26 5.2. Aplicação utilizando o serviço de eventos ................................................................................................... 26 5.2.1. Emissor ...................................................................................................................................................... 27 5.2.2. Receptor ..................................................................................................................................................... 29 5.2.3. Exercício .................................................................................................................................................... 31 7. Bibliografia adicional .......................................................................................................................................... 32 1
1. OBJECTIVO
Familiarização com o ambiente CORBA para Java e com o
desenvolvimento de aplicações. Este documento descreve o método para a criação de
aplicações CORBA e o conjunto de classes e funções disponíveis em Java. Adicionalmente, é
apresentado o código integral de algumas aplicações que pode ser introduzido seguindo as
instruções do enunciado, facilitando a aprendizagem.
2. A ARQUITECTURA CORBA
A arquitectura CORBA (Common Object Request Broker Arquitecture) foi desenvolvida
para facilitar o desenvolvimento de aplicações distribuídas numa rede heterogénea. A
arquitectura CORBA usa uma abordagem orientada para objectos para a criação de software.
Cada objecto encapsula os detalhes da sua implementação oferecendo uma interface bem
conhecida, descrita por um ficheiro de especificação IDL (Interface Definition Language).
O ORB (Object Request Broker) é o componente da arquitectura que liga as aplicações
clientes com os objectos que pretendem usar. O programa cliente não precisa de saber se o
objecto reside na mesma máquina, ou está localizado noutro computador na rede. O programa
cliente só necessita de saber o nome do objecto e de saber usar a interface do objecto. O ORB
trata de todos os detalhes de localizar o objecto, enviar os pedidos e receber as respostas.
Programa Cliente
Objecto A
Invocação bind()
do objecto A
ORB localiza objecto
A e liga-o ao cliente
ORB
Após realizar a associação a um objecto (bind), o cliente pode invocar transparentemente
qualquer função da interface do objecto A, pois o ORB vai encarregar-se de codificar os
parâmetros de entrada da função numa mensagem, vai enviá-la para a máquina onde se encontra
a correr o objecto A, e vai invocar a função sobre o objecto A. O retorno de parâmetros ou
valores também é enviado através do ORB. A utilização do ORB oferece uma total
transparência na invocação de operações entre objectos, sendo possível interligar objectos
realizados em diferentes linguagens (Java, C, C++, Cobol, etc.) ou arquitecturas (ex. Intel,
Linux, Unix, etc.).
O ORB não é um processo separado mas uma colecção de bibliotecas que se ligam aos
executáveis e de recursos computacionais suportados por aplicações. O JDK 1.4.2 inclui um
ORB compatível com a versão 2.4 da norma CORBA e o serviço de nomes. Nesta disciplina foi
adoptada uma realização de código aberto de um ORB, o OpenORB. Em relação ao JDK tem a
vantagem de ser uma realização completa da arquitectura CORBA.
Neste documento não se estuda toda a arquitectura CORBA com os vários serviços e
funcionalidades suportados introduzindo-se apenas os serviços e funcionalidades necessários
para a realização do trabalho prático proposto.
2
3. CONSTRUÇÃO DE APLICAÇÕES CORBA
O primeiro passo no processo de desenvolvimento de aplicações consiste na identificação
dos objectos que fazem parte da aplicação (geralmente um cliente e um servidor). Em seguida
seguem-se os passos indicados na figura:
1. Escreve-se um ficheiro 'Nome.idl' com a especificação da interface de cada objecto
servidor;
2. Usa-se o compilador de IDL (“idl2java -d . Nome.idl” no OpenORB) para
gerar vários ficheiros (caso a interface esteja num módulo, os ficheiros são gerados
dentro de uma directoria com o nome do módulo):
• 'Nome.java' – mapeamento da interface CORBA numa interface Java ;
• 'NomeHolder.java' – suporta parâmetros de saída e entrada-saída dos métodos;
• 'NomeHelper.java' – suporta várias funções auxiliares, como conversão de tipos
explícita (narrow), traduções de e para uma variável do tipo any (insert e
extract), escrita e leitura em feixes de dados, etc.;
• '_NomeStub.java' – stub para o cliente;
• 'NomeOperations.java' – declaração de interface Java com os métodos
suportados na interface CORBA;
• 'NomePOA.java' – skeleton para o objecto;
• 'NomePOATie.java' – realização alternativa de skeleton;
3. Programa-se o código do cliente em 'Cliente.java'. Para usar o objecto define-se
uma variável do tipo referência para objecto (Nome) definido em 'Nome.java'. Após
inicializada, a variável permite realizar todas as operações definidas na interface;
4. Programa-se o código do servidor estendendo a classe abstracta pura (com o nome
'NomePOA') definida em 'NomePOA.java', onde se define a implementação de todas
as funções (declaradas em 'NomeOperations.java'). O objecto só fica activo após
se registar no POA (Portable Object Adapter), o componente do ORB que recebe as
invocações;
5. Compila-se os programas cliente e servidor utilizando o compilador de Java;
6. Arranca-se o servidor;
7. Arranca-se o cliente, que utilizando o ORB comunica com o objecto no servidor.
1
Compilador IDL
Nome.idl
2
Nome.java
_NomeStub.java
_NomeStub.java
3
NomePOA.java
NomePOA.java
Cliente.java
Compilador java
Compilador Java
5
Cliente
Código
Cliente
4
Servidor.java
5
Stub
IDL
Skeleton
IDL
7
Código
Servidor
Servidor
6
ORB
No ambiente NetBeans é necessário realizar a compilação dos vários ficheiros IDL na linha
de comando, integrando posteriormente estes ficheiros no projecto NetBeans onde se desenvolve
o cliente e o servidor.
3
3.1. Definição de interfaces (IDL)
A norma CORBA define um conjunto de tipos básicos para a definição de interfaces, que
inclui os tipos indicados na tabela seguinte com a correspondência para Java:
Tipo IDL
Tipo Java
float
float
double
double
unsigned long / long
int
unsigned
short
/ short
short
boolean
boolean
string / wstring
java.lang.String
Para além dos tipos básicos, o programador pode definir novos tipos a partir de tipos
existentes, ou criar novos tipos estruturados.
A instrução typedef define um novo tipo (NomeTipo) a partir de Tipo:
typedef
Tipo
NomeTipo;
A instrução struct define um tipo estruturado equivalente à struct da linguagem C com o
nome NomeTipo, composto por vários campos:
struct NomeTipo {
TipoCampo_1 NomeCampo_1;
…
};
Depois de compilar o IDL, dá origem à declaração de uma classe com o nome NomeTipo,
com membros de acesso público.
Os arrays de tamanho fixo são declarados de uma forma equivalente à usada em C++,
dando origem à declaração de um array em Java no código gerado pelo compilador de IDLs:
typedef
short
NomeTipo[10];
Os arrays de tamanho variável (no exemplo de inteiros com um tamanho máximo opcional
de 10) são declarados com a palavra chave sequence e dão origem à declaração de um array
em Java no código gerado pelo compilador de IDLs:
typedef
sequence < long, 10> NomeTipo;
Os tipos enumerados têm uma sintaxe semelhante ao C++:
enum TipoEnum { UM, DOIS };
Como não existe este tipo em Java, é gerada uma classe com o nome do tipo enumerado,
que associa a cada membro da enumeração um valor inteiro constante (final int) e um do
tipo da classe gerada. O valor inteiro é usado em instruções switch ou para indexar arrays,
enquanto a segunda versão é usada em parâmetros para funções, para validação de tipos. São
fornecidos métodos para converter entre os dois formatos (value e from_int), e para
devolver uma string com o nome do membro (toString). Para o exemplo anterior seria
gerada a seguinte classe:
4
public final class TipoEnum implements
org.omg.CORBA.portable.IDLEntity
{
/** Enum member UM value */
public static final int _UM = 0;
/** Enum member UM */
public static final TipoEnum UM = new TipoEnum(_UM);
/** Enum member DOIS value */
public static final int _DOIS = 1;
/** Enum member DOIS */
public static final TipoEnum DOIS = new TipoEnum(_DOIS);
/** Internal member value */
private final int _TipoEnum_value;
/** Private constructor */
private TipoEnum( final int value )
{ _TipoEnum_value = value; }
/** Return the internal member value */
public int value()
{ return _TipoEnum_value; }
/** Return a enum member from its value */
public static TipoEnum from_int(int value)
{
switch (value) {
case 0 : return UM;
case 1 : return DOIS;
} throw new org.omg.CORBA.BAD_OPERATION();
}
/** Return a string representation */
public java.lang.String toString()
{
switch (_TipoEvento_value) {
case 0 : return "UM";
case 1 : return "DOIS";
} throw new org.omg.CORBA.BAD_OPERATION();
}
}
A norma CORBA define um tipo genérico (org.omg.CORBA.Any), que pode transportar
qualquer tipo de dados. Este tipo é particularmente útil no serviço de trading, com valores de
propriedades dinâmicos, e no serviço de eventos. O tipo Any é realizado por uma classe
abstracta, que memoriza o tipo dos dados, os dados, e um conjunto de funções para inserir
valores ou para os exportar para uma variável de um tipo básico.
Os objectos do tipo Any são criados utilizando-se o método create_Any() do objecto
ORB. O preenchimento e leitura do valor pode ser realizado de duas maneiras, dependendo do
tipo do objecto a colocar:
•
Num tipo predefinido CORBA, a classe Any fornece dois métodos (e.g. int):
public void insert_int(int value)
throws org.omg.CORBA.BAD_OPERATION;
public int extract_int()
throws org.omg.CORBA.BAD_OPERATION;
5
•
Num tipo definido pelo utilizador (e.g. usertype), são criados dois métodos na
classe usertypeHelper que é sempre criada durante o processo de compilação
do IDL:
public static void insert(Any a, usertype value);
public static usertype extract(Any a);
A classe Any oferece o método type() para obter o tipo de objecto transportado.
No exemplo da secção 6.1 pode encontrar um exemplo de utilização do tipo any.
Adicionalmente, a norma CORBA define outros tipos de dados estruturados (unions, arrays
de tamanho variável (designados de sequências), etc.) que não são usados no trabalho prático.
A definição de uma interface é realizada numa secção precedida da palavra-chave
interface, contendo uma lista de uma ou mais funções que podem ser invocadas num objecto.
Cada função define o tipo do valor retornado pela função, ou void caso não retorne nada.
Adicionalmente define uma lista de argumentos de tamanho variável. Todas as invocações de
funções são fiáveis. No caso de uma função não devolver nada ao cliente pode-se declarar como
oneway, tendo nesse caso uma semântica de "melhor-esforço", sem garantias de fiabilidade na
invocação.
interface NomeInterface {
TipoRetorno NomeFunção(lista de argumentos);
oneway void NomeFunção(lista de argumentos);
};
A lista de argumentos é constituída por um conjunto de argumentos separados por vírgulas
precedidos de uma palavra-chave:
• in TipoArg Nome
• out TipoArg Nome
• inout TipoArg Nome
A palavra-chave define se o argumento é enviado do cliente para o objecto (in), se é
enviado do objecto para o cliente (out), ou se é enviado e depois modificado e recebido no
cliente (inout).
Os servidores podem ainda notificar os clientes de falhas graves no objecto através do
retorno de excepções para os clientes. Neste caso, a interface inclui a definição do nome da
excepção e do conjunto de campos (eventualmente vazio) que compõem a excepção (acedidos
de forma semelhante aos campos de uma estrutura). Caso se use excepções de utilizador, tem de
se definir quais as funções que retornam excepções (raises):
interface NomeInterface {
exception NomeExcepção {lista de campos};
…
TipoRetorno NomeFunção(lista de argumentos) raises (NomeExcepção);
};
Por vezes, quando há várias interfaces que partilham os mesmos tipos, há vantagens em
declará-las integradas num único módulo. A sintaxe é a seguinte:
module NomeMódulo {
definição de uma ou mais interfaces ou de tipos
};
Após compilação do IDL, o módulo dá origem a um package Java, dentro de uma directoria
com o nome do módulo, que contém internamente as declarações de interfaces e de tipos
declarados no módulo. Assim, para um módulo de nome Banco com um tipo DadosConta e uma
interface Conta são criados respectivamente: um tipo Banco.DadosConta (tipo DadosConta
dentro do package Banco) e uma classe Banco.Conta (interface Conta dentro do package
Banco).
6
Todas as declarações de tipos e de interfaces num ficheiro IDL dão origem à criação de
duas classes adicionais durante a sua compilação (e.g. Tipo): TipoHolder e TipoHelper.
Todos os tipos predefinidos do CORBA também têm as classes Holder e Helper
correspondentes.
A classe TipoHolder foi introduzida para resolver o problema do Java não suportar a
passagem de parâmetros por referência – apenas por valor. Sempre que um parâmetro de um
método é out ou inout, é usada uma variável do tipo TipoHolder na invocação do método, ou
na realização do método no servidor. Esta classe tem uma variável interna pública (value)
onde se preenche o valor retornado pelo método, mais dois construtores e funções para escrever
e ler o valor de e para um feixe IIOP:
final public class TipoHolder
implements org.omg.CORBA.portable.Streamable
{
/** Internal Tipo value */
public Tipo value;
/** Default constructor */
public TipoHolder() { }
/** Constructor with value initialisation */
public TipoHolder(Tipo initial)
{ value = initial; }
/** Read Tipo from a marshalled stream */
public void _read(org.omg.CORBA.portable.InputStream istream)
{ … }
/** Write Tipo into a marshalled stream */
public void _write(org.omg.CORBA.portable.OutputStream ostream)
{ … }
/** Return the Tipo TypeCode */
public org.omg.CORBA.TypeCode _type()
{ … }
}
A classe TipoHelper foi introduzida para lidar com tipos numa linguagem fortemente
tipificada, como é o Java. Todos os objectos CORBA herdam da classe
(org.omg.CORBA.Object), i.e. são objectos desta classe. A classe TipoHelper inclui
métodos estáticos para: converter explicitamente o tipo de uma referência para objecto para
Tipo (devolvendo null caso não seja desse tipo); lidar com variáveis do tipo Any; serializar
os dados de objectos da classe Tipo; e para obter informação acerca do tipo (código de tipo e
identificação do repositório de interfaces):
public class TipoHelper
{
/** Insert Tipo into an any */
public static void insert(org.omg.CORBA.Any a, Tipo t)
{ … }
/** Extract Tipo from an any */
public static Tipo extract(org.omg.CORBA.Any a)
{ … }
/** Return the Tipo TypeCode */
public static org.omg.CORBA.TypeCode type()
{ … }
/** Return the Tipo IDL ID */
public static String id()
7
{ return _id; }
/** Read Tipo from a marshalled stream */
public static Tipo read(org.omg.CORBA.portable.InputStream istream)
{ … }
/** Write Tipo into a marshalled stream */
public static void write(org.omg.CORBA.portable.OutputStream ostream,
Tipo value)
{ … }
/** Narrow CORBA::Object to Tipo */
public static Tipo narrow(org.omg.CORBA.Object obj)
{ … }
}
3.2. Clientes (invocação de funções)
Para cada interface, o compilador de IDL gera uma interface Java com o mesmo nome da
interface CORBA, e que é realizada num procurador de um objecto. Por exemplo, para o
ficheiro 'Hello.idl' seguinte:
module HelloApp {
interface Hello {
string sayHello();
};
};
A interface gerada no ficheiro 'HelloApp/Hello.java' realiza os métodos de
org.omg.CORBA.Object, a super-classe de todos os objectos CORBA, e de
HelloApp.HelloOperations:
package HelloApp;
public interface Hello extends HelloOperations, org.omg.CORBA.Object,
org.omg.CORBA.portable.IDLEntity
{ }
A interface HelloOperations define o método sayHello. O stub de cliente (classe
_HelloStub) realiza esta interface, e permite que o cliente invoque uma operação no objecto
remoto transparentemente.
package HelloApp;
public interface HelloOperations {
public String sayHello();
}
A figura seguinte sistematiza a relação entre as várias classes e interfaces usadas na
realização do cliente para uma interface genérica com o nome InterfaceName:
8
3.2.1. Referências para objectos
Todos os objectos CORBA implementam a interface org.omg.CORBA.Object. Através
desta interface têm acesso a algumas funções oferecidas pelo ORB. As restantes são acessíveis
utilizando uma referência para a pseudo-interface do ORB (orb da classe
org.omg.CORBA.ORB). Nesta secção são ilustradas algumas das operações disponíveis, para
o caso de uma referência para objecto if_ref do tipo HelloApp.Hello.
Converte uma referência para objecto numa string no formato IOR (String IOR).
Esta função requer a referência para o ORB (orb) inicializada previamente:
•
IOR= orb.object_to_string(if_ref);
Usa uma string no formato IOR (String IOR) para inicializar uma referência para
objecto. Esta função requer uma referência para o ORB (orb) inicializada previamente
e uma referência genérica (org.omg.CORBA.Object obj):
•
obj = orb.string_to_object(IOR);
if_ref= HelloApp.HelloHelper.narrow (obj);
Testa se o objecto referenciado não existe. Contacta servidor e devolve TRUE se não
existir, podendo gerar excepção:
•
if (!if_ref._non_existent()) { /* não existe */ }
Testa se duas referências para objecto referenciam o mesmo objecto. Caso existam
várias referências para o objecto encadeadas, o método pode retornar false mas serem
referências para o mesmo objecto:
•
if (!if_ref._is_equivalent(obj)) { /* é equivalente */ }
3.2.2. Realização de um cliente
A realização de um cliente que faz uma invocação remota num objecto requer quatro fases:
1.
2.
3.
4.
Inicializar o ORB;
Inicializar a referência para o objecto;
Invocar a operação sobre o objecto;
Libertar a referência, se necessário.
9
A inicialização do ORB (org.omg.CORBA.ORB orb) é feita utilizando métodos
estáticos da classe ORB:
public static ORB init(String[] args, Properties props);
ou
public static ORB init();
A inicialização da referência para objecto (if_ref) pode ser feita: recebendo uma string
com o IOR do objecto pretendido (ver secção anterior); ou utilizando o serviço de nomes (ver
secção 3.5).
A invocação da operação sayHello é feita com a mesma sintaxe que seria num objecto
local. No entanto como pode envolver comunicação por rede, é necessário acrescentar o código
para apanhar as excepções a informar sobre problemas na invocação da função:
try {
System.out.println(if_ref.sayHello());
} catch ( org.omg.CORBA.SystemException e ) {
System.err.println("Excepção: "+e); …
}
3.3. Servidores (recepção de invocações)
Os servidores são realizados como um conjunto de objectos CORBA, cada um associado a
uma interface, registados no adaptador de objectos POA (Portable Object Adapter). No trabalho
prático da disciplina vai ser usado o OpenORB 1.4.0 para Java, um ambiente CORBA de código
aberto compatível com a versão 2.4.2 da norma.
3.3.1. Realização de um servidor
Um servidor é realizado através de um objecto Java que é registado no POA. Este objecto é
construído a partir da classe InterfaceNamePOA (resultante da compilação de um IDL com
o nome InterfaceName), acrescentando-se os métodos suportados na interface (declaradas
num ficheiro com o nome InterfaceNameOperations também gerado pelo compilador
de IDL) e pelo menos um construtor. Este modelo de herança é o mais comum no
desenvolvimento de servidores. Um segundo modelo alternativo, baseado em delegação (Tie
approach), recorre a uma classe de interface ao POA (definida em InterfaceNamePOATie,
também gerada pelo compilador de IDL) que invoca explicitamente métodos num objecto do
programador, que realiza todas as funções de interface. A figura seguinte sistematiza a relação
entre as várias classes e interfaces usadas na realização do servidor:
10
Nas linhas seguintes vai ser introduzido um exemplo completo de realização de um
servidor. Considere-se novamente o ficheiro IDL Hello.idl:
module HelloApp {
interface Hello {
string sayHello();
};
};
A compilação do IDL dá origem à criação de uma classe HelloApp.HelloPOA, que
realiza o skeleton. Para realizar um objecto servidor vai-se definir uma classe (HelloImpl)
que estende a classe HelloApp.HelloPOA, com um construtor e a implementação da função
sayHello (no caso genérico seriam todas as funções da interface HelloApp.
HelloOperations). Observe-se que na definição do construtor são inicializados os dados
internos do objecto:
public class HelloImpl extends HelloPOA
{
private String salute;
// Constructor
HelloImpl( String salute ) {
This.salute= salute;
}
// stores string
public String sayHello() {
return salute;
}
// Returns string
}
11
Após codificar a classe com a implementação do objecto é necessário:
1. inicializar o ORB e o POA;
2. criar um novo objecto Java do tipo HelloImpl;
3. registar o objecto no POA
4. Chamar a função que prepara o POA para receber invocações
Para evitar escrever sucessivamente o nome completo das classes, vai-se admitir que são
usadas as duas linhas seguintes no restante código desta secção.
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
A inicialização do ORB (ORB orb) e do POA (POA poa) é feita com as instruções
seguintes. Observe-se que a referência para o POA é obtida através da interface do ORB,
pedindo a referência inicial para o serviço "RootPOA". Seria possível criar um POA diferente
com configurações mais específicas a nível da gestão de paralelismo objectos, ou de persistência
de objectos. Para o trabalho proposto vai-se usar a configuração por omissão – objectos não
persistentes, que desaparecem quando se desliga o servidor.
orb= ORB.init(args, null);
// args contém argumentos do comando
poa= POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
Quando se obtém a referência para o POA ele está inactivo. É necessário activá-lo antes de
se poder usá-lo para registar objectos:
poa.the_POAManager().activate();
A criação de uma nova variável deve ser feita com a instrução new, passando os
argumentos do construtor para iniciar o estado do objecto Java:
HelloImpl pt= new HelloImpl("Olá mundo!");
O registo do objecto no POA é feito com a instrução:
org.omg.CORBA.Object obj=
poa.servant_to_reference( pt );
Por fim, é necessário arrancar o ciclo principal do POA. Esta operação é bloqueante, não
deixando que nada corra a partir do instante em que é invocada excepto as funções dos objectos
registados no POA.
orb.run();
Um objecto activo é identificado globalmente pela referência devolvida pelo método
servant_to_reference, de um tipo compatível com org.omg.CORBA.Object, mas
localmente ao servidor, também é identificado pelo ponteiro para o objecto Java (compatível
com org.omg.PortableServer.Servant), vulgarmente denominado servant, ou pelo
identificador de objecto atribuído pelo POA durante o registo (do tipo byte[]).
Em qualquer altura é possível desactivar um objecto CORBA, cancelando o registo do
objecto no POA com a operação deactivate_object. Para isso é necessário obter o
identificador de objecto (OID) do objecto. Uma das maneiras é usar o ponteiro para o objecto
Java (servant) como argumento do método servant_to_id da interface POA.
12
try {
poa.deactivate_object(
poa.servant_to_id(helloImpl));
} catch (org.omg.PortableServer.POAPackage.ServantNotActive e) {
// Servant não está registado
} catch (org.omg.PortableServer.POAPackage.ObjectNotActive e) {
// O objecto CORBA não está activo
} catch (org.omg.PortableServer.POAPackage.WrongPolicy e) {
// o POA não suporta registo de servidores
} catch (SystemException e) {
// Falhou comunicação
}
3.3.2. Geração de excepções de utilizador
Considere-se o ficheiro IDL anterior modificado de maneira a incluir uma excepção de
utilizador:
module HelloApp {
exception UtilizadorInvalido {}; // sem parâmetros
interface Hello {
string sayHello() raises (UtilizadorInvalido);
};
};
Após compilar o IDL esta modificação acrescenta a declaração de mais três classes
(UtilizadorInvalido e os ficheiros Holder e Helper correspondentes) e modifica a
assinatura da função sayHello. A função poderá então retornar um valor (com return) ou
uma excepção com (throw), que deverá ser tratada no código do cliente:
public String sayHello() throws HelloApp.UtilizadorInvalido {
}
if (…utilizador invalido…) // testa se o utilizador existe
throw new HelloApp.UtilizadorInvalidor(); // sai da função
return salute;
// Returns string
}
3.3.3. Modos de activação de objectos no CORBA
Existem vários modos como o POA pode funcionar. O modo descrito na secção 3.3.1 é o
modo mais simples, onde o programador arranca e pára os objectos explicitamente. No entanto,
recorrendo a componentes do CORBA como o repositório de implementações é possível registar
uma implementação de um objecto no POA e deixar que este seja arrancado quando necessário,
podendo-se definir diferentes políticas de activação (um objecto para todas as invocações ou um
objecto novo por cada invocação).
Noutro modo de activação designado de DII (Dynamic Interface Invocation) / DSI
(Dynamic Skeleton Invocation), é usado um serviço de repositório de interfaces para obter as
funções e os parâmetros das funções de um tipo de interface, construindo-se dinamicamente os
stubs e skeletons de uma forma semelhante à que foi usada para enviar e receber mensagens
directamente sobre sockets em disciplinas anteriores.
3.3.4. Integração do CORBA no Java
Para realizar um objecto CORBA num programa com uma interface gráfica é necessário
recorrer a pelo menos duas tarefas: uma para lidar com a recepção de eventos gráficos (toque na
tecla do rato, teclas, sockets, etc.); outra para lidar com a recepção de invocações de métodos
13
CORBA. Para facilitar a programação de servidores CORBA é fornecido o ficheiro
corba_thread.java, que facilita a criação da thread CORBA:
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
public class corba_thread extends Thread {
protected ORB orb;
protected POA poa;
/** Creates a new instance of corba_thread */
public corba_thread(ORB _orb) {
this.orb= _orb;
try{
poa = ( POA ) orb.resolve_initial_references( "RootPOA" );
poa.the_POAManager().activate(); // start POA
} catch (SystemException e) {
System.err.println(e);
} catch(Exception e) {
System.err.println("ERROR: " + e);
}
}
public void run() {
try{
System.out.println("ORB running");
orb.run();
} catch (SystemException e) {
System.err.println("Exception in Corba thread: " + e);
} catch(Exception e) {
System.err.println("Exception in Corba thread: " + e);
}
}
public static ORB init_orb(String args[]) {
try {
ORB _orb= ORB.init(args, null);
return _orb;
} catch (SystemException e) {
System.err.println(e);
} catch(Exception e) {
System.err.println("ERROR: " + e);
}
return null;
}
public ORB orb() { return orb; }
public POA poa() { return poa; }
}
Esta classe oferece um método estático (init_orb) para obter a referência inicial para o
ORB e um construtor que inicia a referência para o POA. O arranque da tarefa CORBA pode ser
realizado com o seguinte código:
ORB orb= corba_thread.init_orb(args);
if (orb == null) …
corba_thread ct= new corba_thread(orb);
ct.start();
14
O facto de haver pelo menos duas tarefas a poderem invocar métodos em paralelo obriga a
ter cuidados especiais na programação de rotinas que acedem à interface gráfica, ou que
manipulam estruturas de dados. Deve-se usar métodos ou troços de código synchronized
sempre que possa haver problemas resultantes do acesso concorrente a partir de várias tarefas.
Tenha em atenção que pode haver o risco do programa parar se um troço de acesso exclusivo for
chamado numa altura onde uma variável já está bloqueada.
public synchronized void Log(String s) {
jTextArea1.insert(s, 0);
System.out.print(s);
}
ou
void f(…) {
synchonized (variavel) {
// Apenas uma tarefa acede à lista neste troço de código
}
}
3.4. Passagem de parâmetros em funções IDL
A passagem de parâmetros de métodos em Java é trivial devido à gestão automática de
memória e à utilização das classes TipoHolder, que existem para todos os tipos.
Comparativamente, a realização dos mesmos métodos em C ou C++ é muitíssimo mais
complicada.
Todos os parâmetros de entrada (in) dos métodos são passados por valor, recebendo um
valor da classe do parâmetro. Os parâmetros de saída (out) e de entrada-saída (inout) são
realizados com objectos das classes TipoHolder, tanto no cliente como no servidor,
funcionando como ponteiros para um objecto que é modificado após a invocação.
O IDL seguinte ilustra um exemplo de um método com valores retornados e com os três
tipos de parâmetros, com tipos genéricos:
interface Exemplo {
tipo0 op(in tipo1 p_in, inout tipo2 p_inout, out tipo3 p_out);
};
Esta
interface
é
compilada
ExemploOperations.java:
para
a
seguinte
interface
no
ficheiro
public interface ExemploOperations
{
public tipo0 op(tipo1 p_in, tipo2Holder p_inout, tipo3Holder p_out);
}
O código do cliente deve declarar variáveis dos tipos declarados na interface anterior
(inicializando o valor dos parâmetros in e inout), invocar a operação e processar os valores
retornados. Um exemplo de código do cliente que chama a função op é:
Exemplo obj = ...;
// obtém referência para o objecto
tipo1 t1= new tipo1();
tipo2Holder t2= new tipo2Holder(…/*valor inicial*/);
tipo3Holder t3= new tipo3Holder();
//chama função
tipo0 ret_val= obj.op(t1, t2, t3);
// usa valores: t2.value, t3.value e ret_val
O código do servidor terá de alocar todos os objectos devolvidos pelo método:
15
public tipo0 op(tipo1 p_in, tipo2Holder p_inout, tipo3Holder p_out) {
// usa p_inout.value e p_in …
p_inout.value= new tipo2(); // Modifica valor de p_inout
p_out.value= new tipo3(); // Preenche valor de p_out
return new tipo0(…/*preenche valor retornado*/);
}
3.5. Serviço de Nomes
O serviço de nomes permite associar um ou mais nomes do tipo CosNaming.Name a um
objecto, possibilitando a sua modificação ao longo do tempo. O nome é constituído por uma
sequência de componentes, cada um composto por dois campos (id e kind):
Module CosNaming {
typedef string Istring;
struct NameComponent {
Istring id;
Istring kind;
};
// array de tamanho variável
typedef Sequence<NameComponent> Name;
};
Embora a norma defina dois campos, normalmente deixa-se o campo kind vazio, usandose apenas um nome para cada elemento da sequência (ex. 'unl/dee/rit2/grupo2'). A uma cadeia
de nomes pode estar associada uma sequência de servidores de nomes, cada um responsável por
cada contexto. A linguagem Java oferece uma maneira simplificada para lidar com os nomes. A
classe NamingContextExt permite converter nomes entre o formato CosNaming.Name e
uma string:
import org.omg.CosNaming.*;
NamingContextExt root= …; // referência para servidor nomes
org.omg.CosNaming.Name name;
String str= "rit2/leilao";
try {
name= root.to_name(str);
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
…// Nome inválido
}
Antes de usar o serviço de nomes, tem de se obter uma referência para um objecto servidor
de nomes. Na maior parte dos ORB é possível obter a referência para o servidor de nomes local
ao ORB usando um método do ORB para pesquisar o serviço "NameService". No entanto,
para que o método funcione, o programa tem de receber um parâmetro a definir o IP e porto do
servidor de nomes, ou o ORB tem de ser configurado. No JDK 1.4.2 esta definição é preenchida
automaticamente. No OpenORB 1.4.0 é necessário fornecer o IP e porto do servidor de nomes.
NamingContextExt root;
try {
root= NamingContextExtHelper.narrow (
orb.resolve_initial_references("NameService") );
if (root == null)
{ /* falhou associação a servidor de nomes */ }
} catch(org.omg.CORBA.SystemException e) {
// … trata erro …
}
16
Por vezes, é necessário escolher qual é o servidor de nomes que prendemos usar como raiz
do espaço de nomes. Neste caso, é possível usar uma forma compacta de IOR para definir uma
referência para o servidor de nomes inicial "corbaloc:iiop:[email protected]:20000/NameService".
Caso seja fornecida uma string ns com o endereço IP e o porto (e.g. 172.16.33.1:20000) poderse-ia fazer a inicialização com o seguinte código:
NamingContextExt root;
try {
root= NamingContextExtHelper.narrow (
orb.string_to_object("corbaloc:iiop:1.2@" + ns + "/NameService"));
if (root == null)
{ /* a referência está incorrecta */ }
} catch ( org.omg.CORBA.BAD_PARAM e) {
… // Nome inválido
} catch(org.omg.CORBA.SystemException e) {
// … trata erro …
}
A interacção com o serviço usa funções da interface CosNaming::NamingContext
para realizar três tipos de acções:
• registo de nomes;
• pesquisa de nomes;
• cancelamento de nomes.
A associação de um objecto (Hello ola) a um nome pode ser realizado utilizando a
função bind ou a função rebind. A diferença está no que ocorre quando se tenta registar um
nome que já existe. No primeiro caso (representado) devolve uma excepção
(CosNaming.AlreadyBound) enquanto que com rebind substitui o registo anterior.
NamingContextExt root= …;
String nome= …;
// Regista nome
try {
root.bind(root.to_name(nome), ola);
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
… // nome inválido
} catch (org.omg.CosNaming.NamingContextPackage.NotFound e) {
… // Contexto não existe ?
} catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) {
… // Falhou registo
} catch (org.omg.CosNaming.NamingContextPackage.AlreadyBound e) {
… // só para bind!
} catch (SystemException e) {
… // Falha na comunicação com servidor de nomes
} catch (Exception e) {
… // Erro
}
Caso um nome tenha vários componentes, convém criar o contexto (com
bind_new_context) com o caminho até ao último nome antes de registar um novo nome:
17
// Testa se contexto está criado
int n= nome.lastIndexOf('/');
if (n > -1) {
String pnome= nome.substring(0, n);
try {
root.bind_new_context(root.to_name(pnome));
} catch( AlreadyBound ab ) { // Ignore
} catch (SystemException e) {
… // Falha na comunicação com servidor de nomes
} catch (Exception e) {
… // Erro
}
}
A pesquisa de nomes usa a função resolve para localizar um nome, podendo retornar a
excepção CosNaming.NotFound caso o nome não exista. Após obter a referência obj, é
necessário convertê-la para uma referência para o tipo pretendido utilizando uma mudança
explícita de tipo com o método narrow da classe Helper correspondente.
try {
org.omg.CORBA.Object obj= root.resolve(root.to_name(nome));
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
… // Nome inválido
} catch (org.omg.CosNaming.NamingContextPackage.NotFound e) {
… // Não existe contexto
} catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) {
… // Falhou operação
} catch (SystemException e) {
… // Falhou comunicação com servidor
} catch (Exception e) {
… // Erro
}
O cancelamento de nomes usa a função unbind para libertar o nome, podendo retornar a
excepção CosNaming.NotFound caso o nome não exista:
try {
root.unbind(root.to_name(nome));
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
… // Nome inválido
} catch (org.omg.CosNaming.NamingContextPackage.NotFound e) {
… // Não existe contexto
} catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) {
… // Falhou operação
} catch (SystemException e) {
… // Falhou comunicação com servidor
} catch (Exception e) {
… // Erro
}
Recomenda-se uma leitura do exemplo 5.1 sobre o serviço de nomes.
3.6. Serviço de Eventos
O serviço de eventos suporta a comunicação anónima entre objectos. A comunicação é
realizada através do envio de mensagens de tipo genérico (any) através de um canal de eventos.
Cada canal de eventos está associado a um servidor, identificado por um nome, que oferece
interfaces procuradoras, tanto para os fornecedores como para os consumidores de eventos.
18
canal eventos
fornecedor
consumidor
notificação
notificação
procurador
consumidor
notificação
procurador
fornecedor
O serviço suporta dois modelos de interacção:
• Push (empurrar): os consumidores registam uma interface PushConsumer no canal de
eventos; os fornecedores invocam o método "push" sobre o procurador de consumidor;
O canal de eventos invoca o mesmo método sobre os consumidores.
• Pull (puxar): os fornecedores registam uma interface PullSupplier no canal de
eventos; 2. os consumidores invocam o método "pull" sobre o procurador de
fornecedor; O canal de eventos invoca o mesmo método sobre os fornecedores ou usa
mensagens recebidas anteriormente.
No trabalho apenas vai ser usado o modelo Push, sendo usadas as duas seguintes interfaces
para o envio e recepção de eventos:
module CosEventCom {
exception Disconnected {};
…
interface PushConsumer {
void push(in any data) raises(Disconnected);
void disconnected_push_consumer();
};
interface PushSupplier {
void disconnected_push_supplier();
};
};
Cada fornecedor oferece uma interface do tipo PushSupplier e cada consumidor uma
interface do tipo PushConsumer. Para além do método push, para enviar eventos, as
interfaces oferecem métodos para os fornecedores e consumidores possam ser desligados do
canal.
Os canais de eventos oferecem uma interface CosEventChannelAdmin que permite
obter as referências para os procuradores de consumidores e de fornecedores, e estabelecer as
ligações ao canal:
module CosEventChannelAdmin {
exception AlreadyConnected {};
exception TypeError {};
interface ProxyPushSupplier : CosEventComm::PushSupplier {
void connect_push_consumer(
in CosEventComm::PushConsumer push_consumer
) raises(AlreadyConnected, TypeError);
};
interface ProxyPushConsumer : CosEventComm::PushConsumer {
void connect_push_supplier(
in CosEventComm::PushSupplier push_supplier
) raises(AlreadyConnected);
};
…
interface ConsumerAdmin {
ProxyPushSupplier obtain_push_supplier();
19
…
};
interface SupplierAdmin {
ProxyPushConsumer obtain_push_consumer();
…
};
interface EventChannel {
ConsumerAdmin for_consumers();
SupplierAdmin for_suppliers();
};
};
Um consumidor de eventos começa por invocar o método for_consumers de modo a
obter uma referência para uma interface de gestão (ConsumerAdmin), onde é possível obter
uma referência para a interface de um objecto procurador de fornecedor. Por fim, nesta interface
cria uma ligação ao canal com o método connect_push_consumer.
Um fornecedor de eventos começa por invocar o método for_suppliers de modo a
obter uma referência para uma interface de gestão (SupplierAdmin), onde é possível obter
uma referência para a interface de um objecto procurador de consumidor. Por fim, caso pretenda
ser conhecido pelo procurador (para ser avisado em caso de desconexão), cria uma ligação ao
canal com o método connect_push_supplier.
O OpenORB oferece uma interface adicional para se poder criar dinamicamente canais
(org.openorb.event.EventChannelFactory).
ORB orb = …;
POA poa = …;
org.omg.CORBA.Object obj= null;
org.omg.CosEventChannelAdmin.EventChannel canal = null;
try {
obj = orb.string_to_object(
"corbaname:rir:#COS/EventService/EventChannelFactory" );
} catch ( org.omg.CORBA.BAD_PARAM e) {
… // Nome inválido
} catch(org.omg.CORBA.SystemException e) {
… // Trata erro
}
if ( obj == null ) {
… // Serviço de eventos não está activo
}
// Cria ou associa-se a canal com nome "Rit2"
try { // Tenta criar o canal
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).create_channel( "Rit2" );
System.out.println("Created channel Rit2\n");
} catch ( org.openorb.event.NameAlreadyUsed ex ) {
try { // Associa-se a canal
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).join_channel( "Rit2" );
System.out.println("Joined channel Rit2\n");
} catch ( Exception e ) {
… // Erro
}
} catch ( Exception e ) {
… // Erro
}
20
Recomenda-se uma leitura do exemplo 5.2 sobre a utilização destas interfaces num
fornecedor e num consumidor de eventos.
4. OPENORB
O OpenORB 1.4.0 é uma realização da arquitectura CORBA em código aberto, compatível
com a norma 2.4.2, e disponível na Internet a partir do endereço http://openorb.sourceforge.net.
O pacote inclui o ORB e um conjunto alargado de serviços:
• Serviço de nomes;
• Serviço de trading;
• Serviço de Concorrência e Controlo;
• Serviço de eventos;
• Serviço de Notificação;
• Serviço de persistência de estado;
• Serviço de propriedades;
• Serviço de tempo;
• Serviço de transacções.
No trabalho de laboratório vai-se usar apenas o ORB e o serviço de trading. Estes pacotes
podem
ser
descarregados
a
partir
de
http://sourceforge.net/project/showfiles.php?group_id=43608.
Sugere-se que crie a directoria /opt/CORBA; descarregue os ficheiros no formato tgz; e
descomprima os ficheiros para a directoria utilizando o comando "tar xzf nome.tgz". Os
ficheiros contêm um conjunto de ficheiros jar com as classes do núcleo ORB e dos serviços.
Para correr estes serviços é necessário ter um JDK 1.3 ou 1.4, a variável de ambiente
JAVA_HOME definida, e acrescentar a variável de ambiente TCOO_HOME com a directoria
raiz da instalação do OpenORB. O pacote tools inclui um utilitário para realizar a configuração
automática para um interpretador de linha de comandos bash em Linux: "source
/opt/CORBA/tools/bin/setenv"
Antes de usar o OpenORB falta apenas indicar ao JDK qual o ORB activo (no manual do
OpenORB, em http://openorb.sourceforge.net/docs/1.4.0/OpenORB/doc/orb.html#N10469, são
indicados outros métodos alternativos para realizar esta configuração):
cp /opt/CORBA/OpenORB/config/orb.properties $JAVA_HOME/jre/lib
Para compilar um ficheiro IDL (Nome.idl) usa-se a seguinte linha de comando:
idl2java -d . Nome.idl
O servidor de nomes é lançado com a seguinte linha de comandos (no exemplo define o
número de porto). Pode usar o comando ins –h para ver outros parâmetros.
ins -ORBPort=2001 -u
Para compilar um ficheiro ST (Nome.st) usa-se a seguinte linha de comando:
stdl2java –export -d . Nome.st
O servidor de trading é lançado com a seguinte linha de comandos (o número de porto pode
ser modificado). O servidor de trading deve ser lançado após o lançamento do servidor de
nomes.
tradersvc -ORBPort=2002 -u
21
O ambiente NetBeans não suporta o desenvolvimento integrado de aplicações CORBA,
obrigando a realizar a compilação dos ficheiros IDL através da linha de comandos. A partir
desse momento, o desenvolvimento da aplicação passa a poder ser feito no NetBeans desde que
se acrescente os ficheiros jar presentes na lista de classes (CLASSPATH) ao projecto.
5. EXEMPLOS DE APLICAÇÕES
Nesta secção são descritas duas aplicações que ilustram a utilização do CORBA com
serviço de nomes e do serviço de eventos, e do ambiente de programação.
5.1. Aplicação cliente-servidor utilizando o serviço de nomes
Esta secção ilustra o desenvolvimento de uma aplicação CORBA que usa serviço de nomes
no OpenORB, passo a passo, de forma a se aprender a usar o ambiente. Pretende-se criar uma
aplicação muito simples com um servidor gráfico e um cliente em modo de linha de comando,
que realiza um único método, sayHello, que escreve no ecrã do servidor "Olá mundo …" e
retorna a string para o cliente. O primeiro passo é criar um ficheiro Hello.idl :
module HelloApp
{
interface Hello
{
string sayHello();
};
};
5.1.1. Cliente
Começa-se por compilar o ficheiro Hello.idl, e em seguida entra-se no NetBeans para
introduzir a classe HelloClient com o seguinte código:
import
import
import
import
org.omg.CORBA.*;
org.omg.CosNaming.*;
org.omg.CosNaming.NamingContextPackage.*;
HelloApp.*; // The package containing our stubs.
public class HelloClient
{
/** Returns a reference to the naming server */
private static NamingContextExt get_nameserver(ORB orb, String loc) {
try {
org.omg.CORBA.Object obj = orb.string_to_object(
"corbaloc:iiop:1.2@"+loc+"/NameService");
if ( obj == null ) {
System.out.println("The NamingService reference is incorrect.");
System.out.println("Check if the Naming server is running.");
return null;
}
NamingContextExt root = NamingContextExtHelper.narrow(obj);
if (root == null) {
System.out.println("The Naming service root cannot be found.");
System.out.println("Check if the Naming server is running.");
return null;
}
22
return root;
} catch (SystemException e) {
System.out.println("The Naming service root cannot be found.");
System.out.println("Check if the Naming server is running.");
return null;
}
}
public static void main(String args[])
{
if (args.length != 2) {
System.out.println("Usage: java HelloClient NamingServer ServerName");
System.out.println(
"
e.g. java HelloClient 127.1.1.1:2001 rit2/teste");
return;
}
try{
// Create and initialize the ORB
ORB orb= ORB.init(args, null);
NamingContextExt root= get_nameserver(orb, args[0]);
if (root == null) return;
// Resolve the object reference in naming
Hello helloRef;
try {
helloRef= HelloHelper.narrow(root.resolve(root.to_name(args[1])));
} catch (SystemException e) {
System.out.println("ERROR - Server not available : " + e);
return;
}
// Call the Hello server object and print results
String hello;
try {
hello = helloRef.sayHello();
} catch (SystemException e) {
System.out.println("Exception during invocation of server: "+e);
return;
}
System.out.println("Server returned: '"+hello+"'");
} catch (Exception e) {
System.out.println("Exception: "+e);
}
}
}
São definidas duas funções estáticas nesta classe:
• a função get_nameserver obtém a referência para o servidor de nomes;
• a função main inicializa o ORB e as variáveis iniciais, usando os argumentos da
linha de comando (args), pesquisa o serviço de nomes obtendo uma referência para
o servidor, invoca a operação, e finalmente, escreve a string recebida. Recebe com o
argumentos a localização do servidor de nomes e o nome do objecto a invocar.
5.1.2. Servidor
O servidor vai ser desenvolvido como uma aplicação gráfica. Assim, começa-se por criar
uma nova janela (HelloServer do tipo JForm), e por mudar a propriedade Layout para "Y
23
axis". Em seguida deve-se acrescentar um JPanel e um JScroolPanel. Dentro do
JPanel deve-se colocar sucessivamente:
• um JLabel, com a string "Nome: ";
• um JTextField, com a string "rit2/teste", cujo nome deve ser mudado para
jTextNome;
• um JLabel, com a string " NS: "
• um JTextField, com a string "127.1.1.1:20001", cujo nome deve ser
mudado para jTextNS;
• um JToggleButton1, com a string "Activo";
A JScroolPanel deve ser dimensionada para [333,200]. Dentro do
JScroolPanel deve-se colocar uma JTextArea, dimensionada para [315,2000].
Passando agora à edição do ficheiro, deve-se começar por definir a lista de ficheiros a
incluir:
import
import
import
import
import
org.omg.CORBA.*;
org.omg.CosNaming.*;
org.omg.CosNaming.NamingContextPackage.*;
org.omg.PortableServer.*;
HelloApp.*; // The package containing our stubs.
Dentro da classe pode-se começar por definir a função Log e todas as variáveis da classe:
public void Log(String s) {
System.out.print(s);
jTextArea1.append(s);
}
public corba_thread c_thread;
HelloImpl helloImpl;
public ORB orb;
public POA poa;
// POA thread
// Hello object implementation
O arranque do servidor é feito em dois passos: No construtor e na função main arranca-se
com o ORB, o POA e com a thread que vai receber as invocações CORBA.
/** Creates new form HelloServer */
public HelloServer(String args[]) {
initComponents();
orb= corba_thread.init_orb(args); // Start ORB and POA
c_thread= new corba_thread(orb);
poa= c_thread.poa();
c_thread.start(); // Start CORBA thread - ready to start local objects
}
public static void main(String args[]) {
new HelloServer(args).show();
}
O arranque do objecto CORBA é realizado quando se liga o jToggleButton. Após a
criação do objecto, ele é registado no serviço de nomes. Quando se desliga o botão, cancela-se
este registo e desliga-se o objecto. Esta função usa uma função get_nameserver semelhante
à declarada anteriormente no cliente, mas que nesta classe deve ser declarada como um método
do objecto (sem static), e sem o parâmetro orb.
24
private void jToggleButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// Get naming server reference
NamingContextExt root= get_nameserver(jTextNS.getText());
if (root == null) {
jToggleButton1.setSelected(false);
return;
}
if (jToggleButton1.isSelected()) {
// Create server object
helloImpl = new HelloImpl(this);
org.omg.CORBA.Object helloRef= null;
try {
// Regist it in POA
helloRef= poa.servant_to_reference(helloImpl);
} catch(org.omg.PortableServer.POAPackage.ServantNotActive e) {
Log("Object is not active "+e+"\n");
} catch(org.omg.PortableServer.POAPackage.WrongPolicy e) {
Log("Invalid POA policy: "+e+"\n");
}
if (helloRef == null) {
Log("Failed to activate Hello object\n");
jToggleButton1.setSelected(false);
return;
}
try {
// Get context name (directory where name is stored)
String context= jTextNome.getText();
int n= jTextNome.getText().lastIndexOf("/");
if (n != -1) {
context= context.substring(0,n);
}
try {
// make sure context is bound
root.bind_new_context(root.to_name(context));
} catch( AlreadyBound ab ) {
// Ignore
}
// Bind the object reference in naming
try {
root.bind(root.to_name(jTextNome.getText()), helloRef);
} catch( AlreadyBound ab ) {
Log("Did not bound name: "+ab);
// EXERCÍCIO: CORRIGIR ESTA LIMITAÇÃO
}
Log("Server is running\n");
} catch (Exception e) {
Log("Failed to register object name: " + e + "\n");
// EXERCÍCIO: FALTA DESREGISTAR DO POA, E LIBERTAR REFERÊNCIAS
// PARA OBJECTO
}
} else { // Unregister object in Naming service
try {
root.unbind(root.to_name(jTextNome.getText()));
} catch (org.omg.CosNaming.NamingContextPackage.InvalidName e) {
Log("Invalid name : " + e + "\n");
} catch (org.omg.CosNaming.NamingContextPackage.NotFound e) {
Log("Server not found : " + e + "\n");
} catch (org.omg.CosNaming.NamingContextPackage.CannotProceed e) {
Log("Cancelation cannot proceed : " + e);
} catch (Exception e) {
25
Log("Failed to cancel object name: " + e + "\n");
}
// Unregister object in the POA
try {
poa.deactivate_object(poa.servant_to_id(helloImpl));
} catch (org.omg.PortableServer.POAPackage.ServantNotActive e) {
Log("Invalid hello object: " + e);
} catch (org.omg.PortableServer.POAPackage.ObjectNotActive e) {
Log("Hello object not active: " + e);
} catch (org.omg.PortableServer.POAPackage.WrongPolicy e) {
Log("Invalid POA: " + e);
} catch (SystemException e) {
Log("Error deactivating object: "+e+"\n");
}
// Free server object - activates garbage collecting
helloImpl= null;
Log("Server stopped\n");
}
}
Finalmente, falta programar a classe HelloImpl que realiza a função definida na interface
HelloApp.HelloOperations.java. O construtor desta função recebe um apontador
para a janela principal, para poder invocar a operação Log, que ecoa uma string para a janela.
/** HelloImpl.java */
import HelloApp.*;
public class HelloImpl extends HelloPOA
{
HelloServer server;
HelloImpl(HelloServer srv) {
server= srv;
}
public String sayHello()
{
server.Log("Server received invocation 'sayHello()'\n");
return "Olá Mundo! / Hello world!";
}
}
5.1.3. Exercício
O servidor realizado falha quando já existe outro objecto registado com o mesmo nome.
Modifique o programa de maneira a passar a funcionar sempre, informando o utilizador quando
substituir um registo.
5.2. Aplicação utilizando o serviço de eventos
Esta secção ilustra a utilização do serviço de eventos numa aplicação que corre na linha de
comando. Novamente são fornecidas as instruções para realizar, passo a passo, uma aplicação
com um emissor de eventos do tipo string, e de um receptor que recebe esses dados através de
um canal. Neste caso não é necessário usar nenhum ficheiro IDL, uma vez que só vão ser usados
tipos primitivos da arquitectura CORBA. Este exemplo resultou da modificação de um exemplo
distribuído com a plataforma OpenORB, da autoria de Jerome Daniel e Olivier Modica.
26
5.2.1. Emissor
O programa emissor tem um funcionamento simples: no início, liga-se ao canal e fica em
ciclo infinito à espera que o utilizador introduza uma string através do teclado. Sempre que isso
acontece envia a string para o canal de eventos. A programação do programa emissor (ou
fornecedor) é realizado em três classes: a classe PushServer contém a função main e uma
classe interna Event_Thread, que realiza o ciclo de envio de eventos, e a classe
myPushSupplier que realiza o objecto de ligação ao procurador de fornecedor no canal. Foi
necessário utilizar uma tarefa porque vão ser usados dois ciclos: o do POA e o de leitura do
teclado.
Sempre que se realiza um emissor de eventos é necessário fornecer ao canal uma referência
para um objecto de ligação que realiza a interface org.omg.CosEventComm.
PushSupplier. No emissor, este objecto recebe a invocação do canal a informar que a
ligação foi desligada.
public class myPushSupplier
extends org.omg.CosEventComm.PushSupplierPOA
{
private org.omg.CORBA.ORB orb;
/** Reference to the consumer */
private org.omg.CosEventComm.PushConsumer consumer;
/** Constructor */
public myPushSupplier( org.omg.CosEventComm.PushConsumer consumer )
{
this.consumer = consumer;
}
/** Disconnection from supplier */
public void disconnect_push_supplier()
{
consumer.disconnect_push_consumer();
consumer = null;
}
}
A classe PushServer faz as inicializações do ORB, do POA, do canal, do objecto
myPushSupplier, e estabelece a ligação ao canal. Finalmente, arranca com a tarefa que
envia os eventos e fica em ciclo infinito à espera de receber invocações do canal:
public class PushServer
{
public static void main( String args [] )
{
org.omg.CORBA.Object obj = null;
org.omg.CosEventChannelAdmin.EventChannel canal = null;
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init( args, null );
org.omg.PortableServer.POA rootPOA = null;
try {
obj = orb.resolve_initial_references( "RootPOA" );
} catch ( org.omg.CORBA.ORBPackage.InvalidName ex ) {
ex.printStackTrace();
System.exit( 1 );
}
rootPOA = org.omg.PortableServer.POAHelper.narrow( obj );
try {
rootPOA.the_POAManager().activate();
27
} catch ( java.lang.Exception ex ) {
System.out.println( "Internal error..." );
ex.printStackTrace();
}
System.out.println( "Event Service example" );
System.out.println( "Push model / server side" );
try {
obj = orb.string_to_object(
"corbaname:rir:#COS/EventService/EventChannelFactory" );
} catch ( org.omg.CORBA.BAD_PARAM ex ) {
System.out.println( "The EventService cannot be found." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
} catch ( java.lang.Exception ex ) {
System.out.println( "The EventService cannot be found." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
}
if ( obj == null ) {
System.out.println( "The EventService reference is incorrect." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
}
try {
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).create_channel( "Rit2" );
System.out.println("Created channel Rit2\n");
} catch ( org.openorb.event.NameAlreadyUsed ex ) {
try {
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).join_channel( "Rit2" );
System.out.println("Joined channel Rit2\n");
} catch ( java.lang.Throwable error ) {
error.printStackTrace();
System.exit( -1 );
}
} catch ( org.omg.CORBA.SystemException e ) {
error.printStackTrace();
System.exit( -1 );
}
org.omg.CosEventChannelAdmin.SupplierAdmin supplierAdmin =
canal.for_suppliers();
// Get administration object
org.omg.CosEventChannelAdmin.ProxyPushConsumer consumer =
supplierAdmin.obtain_push_consumer(); // Get proxy reference
// Make local supplier object
myPushSupplier supplier = new myPushSupplier( consumer );
try {
consumer.connect_push_supplier( supplier._this( orb ) );
/* supplier._this( orb ) is a compact notation for:
PushSupplierHelper.narrow(rootPOA.servant_to_reference(supplier)) */
} catch ( java.lang.Exception ex_connect ) {
System.out.println( "Unable to connect to consumer" );
ex_connect.printStackTrace();
}
Event_Thread t= new Event_Thread(orb, consumer);
t.start();
orb.run(); /* ORB Loop */
}
}
28
A classe Event_Thread define um ciclo infinito, que lê uma linha do teclado, prepara
um evento e envia-o para o canal.
/** Event supplier thread */
class Event_Thread extends Thread {
private org.omg.CosEventComm.PushConsumer consumer;
private org.omg.CORBA.ORB orb;
/** Constructor */
public Event_Thread(org.omg.CORBA.ORB orb,
org.omg.CosEventComm.PushConsumer consumer) {
this.orb= orb;
this.consumer= consumer;
}
public void run() {
org.omg.CORBA.Any any = orb.create_any();
String s = null;
while ( true ) {
java.io.InputStreamReader inread = new java.io.InputStreamReader( System.in );
java.io.BufferedReader reader = new java.io.BufferedReader( inread );
System.out.print( "Message to send : " );
try {
s = reader.readLine();
} catch ( java.io.IOException ioex ) {
any.insert_string( "Error at PushSupplier" );
}
any.insert_string( s );
try {
consumer.push( any );
} catch ( java.lang.Exception ex ) {
System.out.println( "End of PushSupplier" );
ex.printStackTrace();
return ;
}
}
}
}
5.2.2. Receptor
O programa receptor tem um funcionamento simples: no início liga-se ao canal e regista o
objecto local receptor de eventos; de seguida fica em ciclo infinito no ORB à espera de receber
eventos. A programação do programa receptor (ou consumidor) é realizada em duas classes: a
classe PushClient contém a função main, que faz as inicializações, e a classe
myPushConsumer que recebe os eventos do canal.
Sempre que se realiza um receptor de eventos é necessário fornecer ao canal uma referência
para um objecto de ligação que realiza a interface org.omg.CosEventComm.
PushClient. No receptor, este objecto recebe as invocações do canal com eventos e a
informar que a ligação foi desligada.
29
class myPushConsumer extends org.omg.CosEventComm.PushConsumerPOA
{
private org.omg.CosEventComm.PushSupplier supplier;
/** Constructor */
public myPushConsumer( org.omg.CosEventComm.PushSupplier supplier )
{
this.supplier = supplier;
}
/** Disconnection from consumer */
public void disconnect_push_consumer()
{
supplier.disconnect_push_supplier();
supplier = null;
}
/** Receive event */
public void push( org.omg.CORBA.Any any )
{
String s = any.extract_string();
System.out.println( "Received : " + s );
}
}
A classe PushClient faz as inicializações do ORB, do POA, do canal, do objecto
myPushConsumer, e estabelece a ligação ao canal. Finalmente, fica em ciclo infinito à espera
de receber eventos:
public class PushClient
{
public static void main( String args [] ) {
org.omg.CORBA.Object obj = null;
org.omg.CosEventChannelAdmin.EventChannel canal = null;
System.out.println( "Event Service example" );
System.out.println( "Push model / client side" );
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init( args, null );
org.omg.PortableServer.POA rootPOA = null;
try {
obj = orb.resolve_initial_references( "RootPOA" );
} catch ( org.omg.CORBA.ORBPackage.InvalidName ex ) {
ex.printStackTrace();
System.exit( 1 );
}
rootPOA = org.omg.PortableServer.POAHelper.narrow( obj );
try {
rootPOA.the_POAManager().activate();
} catch ( java.lang.Exception ex ) {
System.out.println( "Internal error..." );
ex.printStackTrace();
}
try {
obj = orb.string_to_object("corbaname:rir:#COS/EventService/EventChannelFactory" );
} catch ( org.omg.CORBA.BAD_PARAM ex ) {
System.out.println( "The EventService cannot be found." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
30
} catch ( org.omg.CORBA.SystemException e ) {
System.out.println( "The EventService cannot be found." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
}
if ( obj == null ) {
System.out.println( "The EventService reference is incorrect." );
System.out.println( "Check if the Event and Naming servers are running." );
System.exit( -1 );
}
try {
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).create_channel( "Rit2" );
System.out.println("Created channel Rit2\n");
} catch ( org.openorb.event.NameAlreadyUsed ex ) {
try {
canal = org.openorb.event.EventChannelFactoryHelper.narrow( obj
).join_channel( "Rit2" );
System.out.println("Joined channel Rit2\n");
} catch ( java.lang.Throwable error ) {
error.printStackTrace();
System.exit( -1 );
}
} catch (org.omg.CORBA.SystemException e) {
System.out.println("Failed channel creation: "+e);
System.exit( -1 );
}
org.omg.CosEventChannelAdmin.ConsumerAdmin consumerAdmin =
canal.for_consumers();
// Get administration object
org.omg.CosEventChannelAdmin.ProxyPushSupplier supplier =
consumerAdmin.obtain_push_supplier(); // Get proxy reference
myPushConsumer consumer = new myPushConsumer( supplier );
try {
supplier.connect_push_consumer( consumer._this( orb ) );
/* consumer._this( orb ) does the same thing as:
PushConsumerHelper.narrow(rootPOA.servant_to_reference(consumer)) */
} catch ( java.lang.Exception ex_connect ) {
System.out.println( "Unable to connect to consumer" );
ex_connect.printStackTrace();
}
orb.run(); /* ORB Loop */
}
}
5.2.3. Exercício
Modifique o serviço de eventos de maneira a passar a enviar uma estrutura com um número
do tipo short e uma string. Para realizar este exercício necessita de criar um ficheiro IDL
com a declaração da estrutura:
struct Tipo {
string str;
short num;
};
31
7. BIBLIOGRAFIA ADICIONAL
Este documento resume uma pequena parte das especificações CORBA necessária para a
realização do trabalho prático. Caso necessite de mais informação do que a fornecida por este
documento recomenda-se a consulta de:
"Java Programming with CORBA", de Gerald Brose, Andreas Vogel, e Keith Duddy, John
Wiley & Sons, Inc., 2001, ISBN: 0-471-37681-7. Disponível da biblioteca da FCT-UNL.
Manuais online do OpenORB disponíveis a partir do URL http://openorb.sourceforge.net e
nos computadores do laboratório
32
Download