Laborator 05

Dezvoltarea de aplicații distribuite folosind tehnologia CORBA

Obiective

  • înţelegerea mecanismului CORBA pentru dezvoltarea unei aplicaţii distribuite, prin apelul la distanţă al metodelor unor obiecte rezidente pe server, funcţionalitate de care clientul este informat prin specificaţia în cadrul unei interfeţe independente de limbajul de programare;
  • cunoașterea capabilităților oferite de serviciile de nume ce pot fi folosite pentru identificarea obiectelor la distanță într-un mediu distribuit;
  • descrierea unui serviciu disponibil prin intermediul unui obiect la distanță folosind IDL;
  • generarea claselor schelet (pe server) și ciot (pe client) prin intermediul compilatorului furnizat de producător;
  • proiectarea și configurarea serverului care implementează metodele unui obiect la distanță;
  • proiectarea și configurarea clientului care invocă metodele unui obiect la distanță.

Cuvinte Cheie

CORBA, CORBA Services, IDL, idlj, orbd, tnameserv, stub, skeleton, servant, OMG, object marshalling, IIOP

Materiale Ajutătoare

CORBA - aspecte generale

CORBA (Common Object Request Broker Architecture), dezvoltată de Object Management Group, este o tehnologie ce oferă o arhitectură independentă de platformă şi de limbajul de programare pentru a dezvolta aplicaţii distribuite, orientate pe obiecte.

Organizaţia Object Management Group, responsabilă pentru tehnologia CORBA, este formată din peste 300 de companii, cuprinzând majoritatea producătorilor de tehnologii distribuite (platforme, baze de date, aplicaţii, diferite utilitare).
Limbajul Java reprezintă o soluţie pentru dezvoltarea de aplicaţii CORBA, argumente în acest sens fiind maparea cu limbajul IDL şi sistemul de colectare a memoriei disponibile (garbage collection).

Obiectele CORBA se pot afla oriunde, comunicând prin intermediul unei reţele de calculatoare, presupunând că maşinile pe care se găsesc sunt conectate la Internet, fiind vizibile în acest spaţiu printr-o adresă publică. CORBA reprezintă astfel un standard ce permite interacţiunea între (colecţii de) obiecte distribuite (şi eterogene) în cadrul unei aplicaţii.

CORBA pune la dispoziţie un mediu pentru dezvoltarea de aplicaţii distribuite, soluţie adoptată atunci când datele folosite sunt distribuite (în baze / depozite de date aflate pe maşini diferite), când procesarea este distribuită (în sisteme de tip Grid / Cloud) sau când utilizatorii sunt distribuiţi.

Distribuirea datelor poate fi motivată prin probleme cu privire la: securitatea datelor, sau separarea datelor din raţiuni istorice (cronologice) sau al semnificaţiei.
Procesarea distribuită urmăreşte obţinerea unui avantaj competitiv prin folosirea prelucrării paralele (acolo unde poate fi realizată o astfel de descompunere în activităţi care să fie executate concomitent). Totodată, specializarea unor maşini (servere pentru baze de date, sisteme grafice) poate reprezenta un motiv pentru a recurge la distribuirea unei aplicaţii.

Caracteristicile sistemelor distribuite pot fi sistematizate astfel:

Criteriu Sisteme Nedistribuite Sisteme Distribuite
comunicaţie rapidă lentă
erori obiectele sunt distruse împreună obiectele sunt distruse separat
acces concurent doar prin fire de execuţie da
securitate da nu

Ţinând cont de faptul că în cadrul aceluiaşi proces comunicaţia între obiecte este semnificativ mai rapidă decât în cazul unei aplicaţii distribuite, trebuie evitată o interacţiune foarte strânsă între obiecte în cadrul proiectării aplicaţiilor ce utilizează prelucrarea paralelă.

Într-un sistem tradiţional, producerea unei erori implică distrugerea tuturor obiectelor create în cadrul procesului respectiv. Pe de altă parte, în cadrul aplicaţiilor distribuite, programatorul trebuie să ia în calcul situaţia în care disponibilitatea obiectelor este diferită (existenţa lor fiind autonomă), aceasta fiind dependentă de comportamentul procesului în care rulează.

Dacă pentru o aplicaţie nedistribuită există posibilitatea de a alege între unul sau mai multe fire de execuţie, garantând un acces secvenţial al obiectelor, respectiv implicând necesitatea folosirii unor mecanisme de sincronizare pentru controlul accesului concurent, în situaţia unui sistem ce rulează pe mai multe maşini, obiectele pot fi accesate de mai mulţi clienţi în mod implicit, astfel încât mecanismele de sincronizare trebuie implementate obligatoriu.

Securitatea este un aspect care trebuie luat în calcul atunci când obiectele sunt distribuite, interacţiunea între acestea trebuind să aibă la bază mecanisme de autentificare spre a se respecta drepturile de accesare ale acestora.

Arhitectura tehnologiei CORBA

CORBA defineşte o arhitectură pentru obiecte distribuite, bazată pe conceptul de cerere a unui serviciu pus la dispoziţie de un obiect distribuit. Serviciile pe care le oferă un obiect distribuit sunt specificate în interfaţa sa, descrisă într-un limbaj de definire al interfeţei dezvoltat de OMG (IDL – Interface Definition Language). Obiectele vor fi identificate prin referinţele la ele, având tipurile specificate în interfaţa corespunzătoare.

Clientul deţine o referinţă către un obiect distribuit (rezident pe server), definit printr-o interfaţă. Serviciul (distribuit) ORB (Object Request Broker) transmite cererea obiectului distribuit pe server şi întoarce rezultatul la client. Pentru a implementa acest comportament, serviciul localizează obiectul în reţea, comunică cererea către acesta, aşteptă rezultatul, pentru ca în momentul în care este disponibil să îl transmită către aplicaţia care l-a solicitat. Aşadar, ORB realizează transparenţa atât faţă de locaţia unde se află implementat obiectul care pune la dispoziţie serviciile cât şi faţă de limbajul de programare în care se scrie cererea (acesta putând fi diferit faţă de limbajul de programare în care este implementat serviciul obiectului), realizând corespondenţa necesară.

Specificaţia CORBA are drept obiectiv portabilitatea atât a clientului cât şi a implementării obiectelor. În acest sens, este definit un API atât pentru clienţii unui obiect distribuit cât şi pentru implementarea obiectului CORBA. Acest obiectiv ar presupune că se poate rescrie cu uşurinţă codul dezvoltat pentru accesarea unui serviciu CORBA al unui producător, astfel încât să poată funcţiona în alte contexte similare. În realitate, aplicaţii de tip client sunt portabile, însă adaptarea în cazul implementărilor obiectelor este mai dificilă pentru a asigura interoperabilitatea cu mai multe produse CORBA.

Pentru asigurarea interoperabilităţii, este definit un protocol IIOP (Internet Inter-ORB Protocol) ce permite clienţilor folosind un produs CORBA realizat de un producător să poată comunica cu obiecte din cadrul altui produs CORBA dezvoltat de orice alt producător. IIOP funcţionează peste orice implementare TCP/IP.

În cadrul unui sistem distribuit, interoperabilitatea este mai importantă decât portabilitatea. Astfel, IIOP este folosit şi în alte sisteme decât în cele care implementează API-ul CORBA. Deoarece toate aceste sisteme folosesc IIOP ca protocol de transport, deşi utilizează API-uri diferite, pot interacţiona între ele.

IIOP este folosit ca protocol de transport pentru o versiune a Java RMI (RMI peste IIOP). Enterprise Java Beans pot folosi IIOP (deoarece sunt definite prin intermediul RMI). Totodată, numeroase servere de aplicaţii utilizează IIOP fără a implementa întreg API-ul CORBA.

Un aspect important în arhitectura CORBA este reprezentat de definirea unui set de servicii distribuite, cunoscute sub numele de COS (CORBA Services), care suportă integrarea şi interacţiunea dintre obiecte distribuite. Ele sunt implementate ca obiecte distribuite CORBA, definite prin interfeţe IDL.

Serviciu Descriere
ciclu de viaţă al obiectului defineşte procesele de creare, ştergere, mutare şi copiere a obiectelor CORBA
serviciu de nume defineşte modul în care se pot asocia obiectelor CORBA nume simbolice
serviciu de evenimente decuplează comunicarea între obiecte CORBA
serviciu de legături stabileşte legături (cu multiplicităţi şi tipuri asociate) între obiecte CORBA
serviciu de externalizare realizează transformarea obiectelor CORBA în / din surse externe
serviciu de tranzacţii coordonează atomicitatea accesului la obiecte CORBA
control al accesului concurent oferă servicii de blocare a accesului la obiecte CORBA pentru asigurarea mecanismului de serializare
serviciu de proprietăţi stabileşte asocierea perechilor de tip nume-valoare cu obiecte CORBA
serviciu de identificare găseşte obiecte CORBA pe baza proprietăţilor care descriu serviciul oferit
serviciu de interogări permite realizarea de interogări cu obiecte CORBA

CORBA este o specificaţie implementată de mai mulţi producători pentru diferite limbaje de programare. Pentru Java, au fost dezvoltate Java ORB (distribuită împreună cu SDK-ul Java, având unele funcţionalităţi lipsă), VisiBroker for Java (dezvoltată de Borland), Orbix Web (de la Iona Technologies), WebSphere (server de aplicaţii realizat de IBM).

Exemplu

În cele ce urmează, implementarea unei aplicaţii distribuite folosind tehnologia CORBA va fi ilustrată pe cazul particular al aplicaţii implementând un serviciu de rezervare, a cărui funcţionalitate a fost descrisă pe larg în laboratorul anterior.

Specificarea funcționalității unui obiect distribuit în IDL

Prin limbajul de definire a interfeţelor dezvoltat în cadrul OMG (IDL – Interface Definition Language) se permite specificarea obiectelor distribuite prin operaţiile suportate, fără a se oferi detalii cu privire la modul în care acestea sunt realizate. Prin urmare, nu se vor specifica în IDL stări ale obiectului sau algoritmi. Implementarea unui obiect CORBA este realizată într-un limbaj de programare, interfaţa fiind doar o punte de legătură (un contract) între codul care foloseşte obiectul (dependent doar de interfață) şi codul care defineşte obiectul. Interfaţa poate fi considerată un contract în sensul garantării unei anumite funcţionalităţi (prin parametrii de intrare şi de ieşire) fără a oferi detalii despre modul de implementare.

Interfaţa în care este specificat comportamentul obiectului distribuit este independentă de limbajul de programare. IDL defineşte corespondenţe pentru mai multe limbaje de programare, permiţând deci ca implementarea unui obiect să se poată face în limbajul de programare adecvat pentru obiectul respectiv. Totodată, acelaşi lucru este posibil şi pentru client.

Au fost standardizate corespondenţe între IDL şi C, C++, Java, Ada, COBOL, SmallTalk, Objective C, Lisp.

Pot fi definite astfel, independent de limbajul de programare, interfeţe modularizate pentru obiecte, specificând atribute şi metode suportate de acestea. De asemenea, pot fi indicate excepţiile pe care le poate genera o anumită operaţie. Fiecare operaţie este definită prin tipul de date al valorii întoarse precum şi prin tipurile de date ale parametrilor.

IDL suportă tipuri de date de bază (boolean, char, octet, short, long, float, double, string) sau construite (struct, union, enum, sequence), obiecte definite sau tipul any pentru obiecte cu tipul de date stabilit dinamic (la momentul utilizării).

Corespondenţa între tipuri de date IDL şi cele din limbajul de programare Java sunt descrise mai jos:

IDL Java
boolean boolean
char / wchar char
octet byte
short / unsigned short short
long / unsigned long int
long long / unsigned long long long
float float
double double
string / wstring String

De asemenea, există o corespondenţă între anumite structuri IDL şi structuri Java.

IDL Java
module package
interface interface
operation method
attribute pair of methods
exception exception

Un concept întâlnit frecvent atunci când se vorbeşte despre corespondenţa tipurilor de date este cel de tipare inversă (eng. marshalling), adică de transformare a unei structuri specifice unui limbaj de programare într-un format CORBA IIOP, astfel încât să poată fi transmisă prin reţea.

Pentru aplicaţia considerată, un exemplu de specificaţie IDL poate fi:

Reservation.idl
module Reservation
{    
    struct Date
    {
        long day;
        long month;
        long year;
    };
    struct Moment
    {
        long hour;
        long minute;
    };
    struct Time    
    {
        Date date;
        Moment moment;
    };
    struct Interval
    {
        Time start;
        Time end;
    };
 
    typedef sequence<Interval> Intervals;
 
    exception UnspecifiedTimeTable {};
 
    interface ReservationService {
       Intervals getTimeTable() raises(UnspecifiedTimeTable);
       long getAvailableSeats(in Interval interval) raises(UnspecifiedTimeTable);
       boolean makeReservation(in long customerId, in Interval interval,  in Interval numberOfSeats) raises(UnspecifiedTimeTable);
       boolean cancelReservation (in long customerId, in Interval interval) raises(UnspecifiedTimeTable);
    }; 
};

În specificaţia IDL Reservation.idl, au fost definite ca structuri de date Date, Moment, Time, Interval, Intervals, şi excepţia UnspecifiedTimeTable, utilizate ulterior în interfaţa ReservationService. Au fost definite mai multe metode ce primesc parametri de intrare (fapt specificat prin cuvântul cheie in), descriind funcţionalitatea oferită de aplicaţie. Parametrii de ieşire (indicând date care sunt transmise de la obiectul distribuit către client) sunt specificaţi prin cuvântul cheie out, iar parametrii de intrare ieşire (indicând date transmise de la client către obiectul distribuit şi înapoi la client) sunt specificaţi prin cuvântul cheie inout.

Produsele CORBA pun la dispoziţie un compilator care converteşte interfaţa IDL în reprezentarea corespunzătoare limbajului de programare. Pentru distribuţia standard Java, acesta se numeşte idlj și se găseşte în directorul bin al distribuţiei standard.

Prin comanda

idlj Reservation.idl

vor fi generate următoarele fişiere:

Fişier Semnificaţie
ReservationService.java Interfaţa IDL reprezentată ca intefaţă Java
ReservationServiceHelper.java Implementează operaţiile de tipare pentru interfaţă (insert, extract, read, write, narrow, unchecked_narrow)
ReservationServiceHolder.java E folosit pentru operaţii cu parametri de tip out şi inout.
ReservationServiceOperations.java Conţine descrierile operaţiilor implementate de interfaţă (ReservationService este derivată din ReservationServiceOperations)
_ReservationServiceStub.java Implementează un obiect local reprezentând obiectul CORBA “la distanţă” ce transmite cererile spre obiectul distribuit
Au fost menţionate doar fişierele cu semnificaţie în dezvoltarea aplicaţiei distribuite, fiind omise fişierele corespunzătoare celorlalte structuri de date, al căror format (ca denumire şi funcţionalitate) este asemănător.

Pentru a genera atât fişierele pentru client şi pentru server, utilitarul idlj este apelat cu parametrul -fall. Pentru a genera doar fişierele pentru server sau pentru client sunt folosiţi parametrii -fserver, respectiv -fclient.

Implicit (dacă utilitarul este apelat fără parametri), sunt generate doar fişierele pentru client.

Etapele dezvoltării unei aplicaţii distribuite folosind CORBA

Aşadar, etapele dezvoltării unei aplicaţii distribuite folosind CORBA sunt:

  • definirea interfeţei care stabileşte funcţionalitatea obiectului distribuit, folosind limbajul de specificare IDL, astfel încât programatorii să poată implementa programe client şi server în orice limbaj de programare compatibil cu CORBA (când se implementează un server sau un client, interfeţele IDL sunt primite de la producător);
  • compilarea interfeţei care conţine funcţionalitatea obiectului distribuit (pentru Java, se foloseşte idlj); se obţine versiunea interfeţei corespunzătoare limbajului de programare respectiv, şi totodată clasele ciot (eng. stub) respectiv schelet (eng. skeleton) care conţin infrastructura de conectare la ORB;
  • dezvoltarea serverului (se folosesc clasele schelet generate); pe lângă implementarea metodelor specificate în interfaţă, trebuie precizat mecanismul de pornire a serviciului ORB şi de aşteptare a invocărilor realizate de client;
  • dezvoltarea clientului (se folosesc clasele ciot generate); clientul porneşte serviciul ORB, caută serverul folosind serviciul de nume oferit de interfaţă pentru a obţine o referinţă către obiectul distribuit, spre a-i apela metodele
  • pornirea aplicaţiilor în următoarea ordine: serviciu de nume, server, client.

Prin serviciul de nume (referinţele către) obiectele distribuite sunt publicate de server, devenind astfel disponibile clienţilor, care le pot folosi pentru a apela metodele specificate în interfaţă. Se va utiliza orbd, un proces care rulează în fundal (eng. daemon) şi conţine serviciile: de iniţializare (Bootstrap Service), de nume tranzitoriu (Transient Name Service) dar şi de nume permanent (Persistent Name Service) şi un proces de gestiune pentru server (Server Manager).

Se poate folosi serviciul de nume (tranzitoriu) tnameserv (apelat fără parametri), ca alternativă la orbd. De regulă, tnameserv se lansează pe portul 900.

Într-un sistem Unix, serviciul de nume se porneşte astfel:

aipi2014@ubuntu:$ orbd -ORBInitialPort 1100&

Într-un sistem Windows, serviciul de nume se porneşte astfel:

C:\Users\Aipi2014> start orbd –ORBInitialPort 1100

Parametrul ORBInitialPort este obligatoriu.

CORBA implementează un sistem de excepţii care este foarte similar celui din limbajul de programare Java.

Există două tipuri de excepţii în CORBA: sistem şi utilizator.

Excepţiile de tip sistem sunt generate atunci când au loc evenimente ce ţin de aplicaţie la modul general (a fost apelată o metodă care nu este implementată pe server, există o problemă de comunicare, sistemul ORB nu a fost iniţializat corespunzător). System Exceptions în Java sunt o subclasă a RuntimeException astfel încât ele nu trebuie incluse într-un bloc try {…} catch. Totuşi, tratarea excepţiilor în acest fel poate beneficia de avantajul obţinerii unui cod de eroare, care diferă de la producător la producător.

Excepţiile de tip utilizator sunt generate în cazul unor erori în cadrul unei metode propriu-zise. User Exceptions în Java sunt o subclasă a Exception, aşadar trebuie incluse (obligatoriu) într-un bloc try {…} catch.

Proiectarea serverului

Implementarea obiectului distribuit CORBA este transparentă pentru client, care cunoaşte doar specificaţiile din interfaţa IDL. Încapsularea completă a obiectelor distribuite CORBA oferă mai multă libertate în ceea ce priveşte implementarea lor, aceasta putând fi realizată în limbaje de programare diferite chiar şi faţă de limbajul de programare în care este scris clientul. La nivel de server, implementarea obiectului distribuit se face pe baza clasei schelet generată la compilarea interfeţei IDL. Se oferă astfel mecanismul de acces la nivelul obiectului distribuit, ce conţine despachetarea datelor primite, apelarea metodei ce implementează operaţia solicitată, împachetarea rezultatelor transmise.

Pentru implementarea obiectului distribuit CORBA, programatorul trebuie să dezvolte o clasă derivată din casa schelet, specificând comportamentul pentru metodele descrise de interfaţa IDL.

CORBA specifică două mecanisme de dezvoltare a serverului pentru implementarea unei interfeţe IDL:

  • modelul moştenirii, care presupune implementarea unei clase care extinde clasa schelet generată de compilator; din acest model fac parte:
    • standardul POA (Portable Object Adapter), dezvoltat de OMG; compilatorul generează o clasă *POA.java care extinde org.omg.PortableServer.Servant, folosită drept clasă de bază pentru toate implementările servantului (clasa care implementează metodele specificate în interfaţa IDL pe server);
Un obiect de adaptare (eng. object adapter) este un mecanism care conectează o cerere la codul care o deserveşte prin intermediul unui obiect referinţă. POA este un tip particular al acestui tip de obiect definit de specificaţia CORBA care îndeplineşte următoarele obiective: permite programatorilor să dezvolte implementări de obiecte care sunt portabile între diferite produse ORB, oferă suport pentru obiecte persistente (din perspectiva clientului, obiectele există pe tot parcursul aplicaţiei menţinând valorile stocate în ele – deşi serverul poate fi repornit sau implementările pot fi realizate prin obiecte diferite), pentru activarea transparentă a obiectelor, permiţând ca un servant să suporte simultan mai multe identităţi de obiecte. Prin POA, implementarea deţine controlul asupra identităţii, stării, stocării, ciclului de viaţă al obiectului.
  • ImplBase – este folosit doar pentru a asigura compatibilitatea cu servere scrise în versiuni Java mai vechi (< 1.4); este recomandată evitarea acestui model în favoarea POA.
  • modelul delegării – interfaţa IDL este implementată folosind două clase
    • o clasă generată de compilator care moşteneşte clasa schelet, dar deleagă toate apelurile unei clase care implementează metodele;
    • o clasă care implementează operaţiile generate din interfaţa IDL (având denumirea *Operations.java).

La nivelul serverului, trebuie realizate următoarele operaţii:

  1. crearea şi iniţializarea instanţei ORB;
  2. obţinerea unei referinţe către obiectul rădăcină POA şi activarea POAManager;
  3. instanţierea servantului (implementarea obiectului distribuit);
  4. obţinerea unei referinţe către contextul de nume în care se va înregistra obiectul distribuit (obţinerea obiectului rădăcină al contextului de nume);
  5. înregistrarea obiectului distribuit în contextul de nume;
  6. aşteptarea invocărilor obiectului distribuit de către client.
Server.java
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.PortableServer.*;
import Reservation.*;
 
public class Server {
 
    public static void main (String[] args) {
 
        try {
 
            ORB orb = ORB.init(args,null);
 
            POA POARoot = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
            POARoot.the_POAManager().activate();
 
            ReservationServiceImplementation ReservationServiceImpl = new ReservationServiceImplementation();            
            org.omg.CORBA.Object ReservationServiceImplRef = POARoot.servant_to_reference(ReservationServiceImpl);
            ReservationService ReservationServiceRef = ReservationServiceHelper.narrow(ReservationServiceImplRef);
 
            org.omg.CORBA.Object nameServiceRef = orb.resolve_initial_references("NameService");
            NamingContextExt nameContextRef = NamingContextExtHelper.narrow(nameServiceRef);
 
            String serviceName = "ReservationService";
            NameComponent nameComponent = new NameComponent(serviceName,"");
            NameComponent path[] = { nameComponent };
 
            nameContextRef.rebind(path,ReservationServiceRef);
 
            orb.run();
 
        } catch (Exception exception) {
            System.out.println ("An exception has occurred: "+exception.getMessage());
        }
    }
}

Clasa ReservationServiceImplementation reprezintă servantul, care extinde clasa ReservationServicePOA, astfel încât moşteneşte funcţionalitatea CORBA generată de compilatorul IDL. Aceasta va conţine toate implementările descrise prin interfaţă, conţinând serviciile puse la dispoziţie.

Un server CORBA are nevoie de un obiect ORB local, ca de altfel şi fiecare client CORBA. Toate obiectele servant vor fi înregistrate prin acesta, astfel încât serviciul ORB să poată localiza serverul atunci când sunt invocate funcţionalităţi pe care le pune la dispoziţie. Prin metoda init(), ce primeşte argumente din linia de comandă, se pot specifica anumite proprietăţi, între care cele mai importante sunt portul şi adresa pe care rulează serverul. Serverul va fi lansat în execuţie astfel:

  • Linux
    aipi2014@ubuntu:$ java Server -ORBInitialPort 1100 -ORBInitialHost localhost&
  • Windows
    C:\Users\Aipi2014> start java Server -ORBInitialPort 1100 -ORBInitialHost localhost

Apoi, prin intermediul serviciului ORB se obţine obiectul care conţine referinţa către obiectul POA rădăcină prin metoda resolve_initial_references(). După obţinerea referinţei către obiectul POA rădăcină se activează serviciul POAManager. Prin metoda activate() se schimbă starea serviciului de gestiune POA în activ astfel încât obiectele asociate acestuia vor prelucra cererile venite de la client. Fiecare obiect POA are asociat un obiect POAManager (care poate gestiona, la rândul lui, unul sau mai multe obiecte POA). Prin POAManager se încapsulează starea în care se găseşte prelucrarea fiecărui obiect POA asociat.

Serverul este procesul care instanţiază unul sau mai multe obiecte servant. Un servant moşteneşte interfaţa generată de compilatorul IDL, implementând propriu-zis prelucrările specificate prin operaţiile respective. Servantul ReservationServiceImplementation este instanţiat prin obiectul ReservationServiceImpl.

Se obţine o referinţă către obiectul distribuit, iniţial ca obiect CORBA (prin metoda servant_to_reference(), apelată din contextul obiectului rădăcină POA, instanţei servantului), cast-ul la tipul corespunzător făcându-se prin metoda (statică) narrow() din clasa (generată de compilatorul IDL) ReservationServiceHelper.

Operaţiile puse la dispoziţie de (instanţa) servant(ului) sunt făcute vizibile către client prin serviciul de nume Common Object Service (COS).

Obiectele pot fi făcute vizibile către client şi prin transformarea într-un şir de caractere a referinţelor către acestea şi publicarea lor într-un fişier.

Se obţine referinţa către acest serviciu de nume astfel încât să se poată înregistra prin ea referinţele către obiectele care implementează diferite interfeţe. Referinţele către obiectele care implementează diferite interfeţe sunt folosite de clienţi pentru invocarea metodelor specificate în interfaţă.

Referinţa către serviciul de nume se face tot prin metoda resolve_initial_references(), transformarea către tipul corespunzător contextului de nume realizându-se prin metoda narrow(), la fel ca în cazul obiectelor POA. Parametrul “NameService” este definit pentru toate serviciile ORB, indicând faptul că serviciul de nume va fi persistent, dacă se foloseşte serviciul de nume orbd, respectiv tranzitoriu, în cazul utilizării tnameserv.

Se realizează o componentă de nume indicându-se denumirea prin care serviciile puse la dispoziţie de obiectul respectiv pot fi accesate. Se face apoi legătura dintre componenta de nume şi referinţa către obiectul distribuit, întoarsă către client prin apeluri tip NamingContext.resolve_str(serviceName), prin care se poate obţine acces către funcţionalitatea dorită.

Apelul orb.run() blochează serverul în aşteptarea invocărilor realizate din contextul clientului. Deoarece apelul este făcut din metoda main(), este folosit firul de execuţie corespunzător acesteia, astfel încât, la momentul încheierii prelucrărilor corespunzătoare unei invocări, serverul va rămâne în continuare blocat în aşteptarea unor alte apeluri. Datorită acestui comportament, trebuie specificată o condiţie de terminare, implicând apelul metodei shutdown() asociată obiectului corespunzător serviciului ORB.

Proiectarea clientului

Operaţiile realizate la nivelul clientului sunt asemănătoare cu cele realizate de către server:

  • crearea şi iniţializarea instanţei ORB – se foloseşte metoda init() la fel ca în cazul serverului, iar parametrii (din linia de comandă) indică, similar, portul şi adresa unde rulează serviciul de nume;
    java Client -ORBInitialPort 1100 -ORBInitialHost localhost

    Orice client are nevoie de un obiect local ORB pentru realizarea împachetării datelor transmise şi prelucrărilor IIOP.

  • obţinerea unei referinţe către contextul de nume folosit pentru identificarea (unei referinţe către) obiectul(ui) distribuit – este apelată metoda resolve_initial_references() din contextul obiectului ORB având parametrul “NameService”, cu aceeaşi semnificaţie ca în cazul serverului, transformarea la tipul dorit realizându-se prin metoda narrow;
  • utilizarea serviciului de nume pentru căutarea obiectului distribuit prin specificarea unei denumiri sub care aceasta a fost înregistrat (de către serverul pe care se găseşte o implementare a sa) – se foloseşte metoda resolve_str() a obiectului care conţine o referinţă contexul de nume, având ca parametru denumirea sub care a fost specificat, în acelaşi mod precum s-a realizat localizarea serviciului de nume;
  • transformarea (referinţei către) obiectul(ui) distribuit CORBA obţinut la tipul corespunzător folosindu-se metoda narrow();
  • apeluri ale metodelor puse la dispoziţie de obiectul distribuit CORBA, specificate în interfaţa IDL – invocările CORBA arată la fel ca apelul unei metode realizată asupra unui obiect local, detaliile cu privire la împachetare şi transmitere fiind transparente către utilizator.
Client.java
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import Reservation.*;
 
public class Client {
 
    public static void main (String[] args) {
        try {
            ORB orb = ORB.init(args,null);
 
            org.omg.CORBA.Object nameServiceRef = orb.resolve_initial_references("NameService");
            NamingContextExt nameContextRef = NamingContextExtHelper.narrow(nameServiceRef);
 
            String serviceName = "ReservationService";
            ReservationService ReservationServiceRef = ReservationServiceHelper.narrow(nameContextRef.resolve_str(serviceName));
 
            // TO DO: apeleaza metode obiect rezervare            
        } catch (Exception exception) { 
            System.out.println ("An exception has occurred: "+exception.getMessage()); 
        }
    }
}

Comparaţie între modelele de programare CORBA implementate în Java

IIOP este un protocol de transport (peste Internet) între obiecte tip ORB având drept scop realizarea interoperabilităţii între limbaje de programare şi aplicaţii comercializate de diverşi producători. Acest protocol este utilizat pentru sisteme distribuite dezvoltate fie în Java RMI, fie în IDL.

Modelul de programare IDL este axat pe interfaţa care descrie metodele care pot fi apelate din procesul “la distanţă”, specificând tipurile de argumente acceptate de procedura invocată cât şi tipurile informaţiilor întoarse de aceasta. CORBA este un sistem independent de limbajul de programare în care valorile argumentelor sau rezultatelor întoarse sunt limitate la ceea ce poate fi reprezentat în limbajele de programare în care se realizeaza implementarea. Tipurile parametrilor de intrare şi de ieşiure trebuie să fie cele specificate în interfaţă. Totodată, orientarea pe obiecte este limitată la obiecte transmise prin referinţă (obiectul nu poate fi transmis între maşini prin cod).

Modelul de programare IDL (JavaTM IDL) conţine Java CORBA ORB şi compilatorul idlj – care realizează corespondenţa între limbajele IDL şi Java. Astfel, este implementată funcţionalitatea CORBA în platforma Java, oferind interoperabilitate şi conectivitate standardizată permiţând aplicaţiilor distribuite să invoce – în mod transparent – operaţii puse la dispoziţie prin reţea folosind IDL şi IIOP. Este folosit un obiect ORB pentru prelucrări distribuite folosind comunicaţie bazată pe IIOP. Folosirea acestui model implică definirea intefeţelor “la distanţă” folosind limbajul IDL (dezvoltat de OMG), compilarea lor folosind idlj care va genera versiunea Java a interfeţei ca şi clasele ciot şi schelet care permit aplicaţiilor să comunice prin ORB. Java IDL este o componentă a distribuţiei standard Java.

Limbajul IDL de specificare a intefeţelor este pur declarativ, proiectat pentru specificarea operaţiilor corespunzătoare unei aplicaţii distribuite independent de limbajul de programare. El cuprinde şi corespondenţele către limbaje de programare ca Java, C, C++, Lisp, Python, Smalltalk, COBOL şi Ada, astfel că fiecare instrucţiune este translatată corespunzător. Astfel, un client dezvoltat în Java se poate folosi de implementările (specificate ca interfaţă IDL) realizate în C++ pentru un anumit serviciu, interoperabilitatea dintre cele două limbaje de programare fiind obţinută prin intermediul ORB.

În RMI, interfaţa şi implementarea ei sunt dezvoltate folosind acelaşi limbaj de programare, astfel încât nu există problema corespondenţei dintre acestea. Obiecte la nivel de limbaj de programare (codul însuşi) pot fi transmise de la un proces la altul.

Modelul de programare RMI se referă la prelucrări distribuite prin API-ul cu acelaşi nume. Se poate lucra doar în limbajul de programare Java, folosind Java Remote Method Protocol (JRMP) sau cu limbaje de programare compatibile CORBA, folosind protocolul IIOP. Acest model de programare este parte din distribuţia standard Java şi conţine serviciul ORB şi compilatorul rmic pentru generarea claselor ciot şi schelet precum şi a legăturilor dintre obiecte la distanţă folosind protocoalele JRMP sau IIOP. Folosind acelaşi limbaj de programare, dezvoltarea este mai rapidă, iar flexibilitatea este obţinută prin posibilitatea transmiterii de obiecte serializabile.

Modelul de programare IDL sau RMI cu componenta IIOP vor fi folosite aşadar doar pentru interfaţarea cu anumite aplicaţii (eng. legacy systems), preferându-se folosirea modelului de programare RMI din motive ce ţin de portabilitate, securitate şi colectarea memoriei disponibile.

Activitate de Laborator

aipi2014-lab05-scripts.zip

aipi2014-lab05-remoteinterfaces.zip

aipi2014-lab05-eclipse.zip

aipi2014-lab05-netbeans.zip

1. [0 puncte] Să se pornească serviciul de nume orbd prin apelarea scripturilor startNameService[.bat|.sh]. În cazul când calea către directorul bin al SDK-ului Java nu este precizată în variabila de mediu PATH, acest script nu va fi executat cu succes.

În cazul când calea către directorul bin al SDK-ului Java nu este precizată în variabila de mediu PATH, acest script nu va fi executat cu succes.

Precizarea căii către directorul bin al SDK-ului Java în variabila de mediu PATH se realizează astfel:

  • Linux
    export PATH=${PATH}:/usr/lib/jvm/default-java/bin
  • Windows Computer Properties → Control Panel Home/Advanced System Properties → System Properties/Advanced/Environment Variables → System Variables / Path / Edit
    SET PATH=%PATH%;C:\Program Files\Java\jdk1.8.0_25\bin





Verificaţi că procesul orbd rulează înainte de a porni serverul.
  • Linux
    aipi2014@ubuntu:~$ ps -a
  • Windows - procesul apare în lista Task Manager

2. [0 puncte] Să se compileze intefaţa Reservation.idl folosind compilatorul idlj pentru generarea claselor server, respectiv client, plasându-se sursele generate în directoare diferite.

  • server
    idlj -fserver Reservation.idl
  • client
    idlj -fclient Reservation.idl

Se observă faptul că la generarea claselor din server lipsesc fişierele ReservationServiceHelper.java şi _ReservationServiceStub.java. Acestea vor trebui preluate din clasele generate pentru client.

Să se includă fișierele generate în codul sursă corespunzător proiectelor pentru server, respectiv client.

Folosind mediul integrat de dezvoltare (Refactor → Rename), să se redenumească pachetul reservation în ro.pub.cs.aipi.lab05.reservation.

Să se creeze configurații de rulare pentru server, respectiv client (dacă nu se folosesc testele unitare), specificând parametrii liniei de comandă:

  • -ORBInitialHost având valoarea localhost;
  • -ORBInitialPort având valoarea 1100.

Astfel, valoarea parametrilor din linia de comandă va fi:

-ORBInitialHost localhost -ORBInitialPort 1100

3. [10 puncte] Să se încarce orarul de funcţionare al restaurantului în constructorul clasei ReservationServiceImplementation cu datele conţinute de fişierul timetable.txt.

Se va parsa conţinutul fişierului timetable.txt care pe prima linie conţine capacitatea (numărul de locuri) încărcată în variabila numberOfSeats iar pe următoarele linii programul pentru fiecare zi calendaristică exprimat sub forma ZZ LL AAAA O1 M1 O2 M2, acesta fiind încărcat în obiectul timetable.

Să se implementeze metoda getTimetable a clasei ReservationServiceImplementation. Această metodă întoarce conţinutul obiectului timetable.

Pentru a reutiliza codul implementat în laboratorul anterior, ar putea fi utilă implementarea unor metode de conversie între clasele Interval / ReservationData deja definite (existente în pachetul entities) şi clasele Interval / ReservationInformation generate din interfaţa IDL (existente în pachetul reservation).

Pentru clasa Interval metodele de conversie trebuie realizate in ambele sensuri.

public ro.pub.cs.aipi.lab05.reservation.Interval entitiesIntervalToReservationInterval(ro.pub.cs.aipi.lab05.entities.Interval interval) {
    ro.pub.cs.aipi.lab05.reservation.Interval result = new ro.pub.cs.aipi.lab05.reservation.Interval();
    // TO DO       
    return result;
}
 
public ro.pub.cs.aipi.lab05.entities.Interval reservationIntervalToEntitiesInterval(ro.pub.cs.aipi.lab05.reservation.Interval interval) {
    ro.pub.cs.aipi.lab05.entities.Interval result = new ro.pub.cs.aipi.lab05.entities.Interval();
    // TO DO
    return result;
}

Pentru clasa ReservationData / ReservationInterval, conversia trebuie realizată numai în sensul ReservationDataReservationInterval.

public ro.pub.cs.aipi.lab05.reservation.ReservationInformation entitiesReservationDataToReservationReservationInformation(ro.pub.cs.aipi.lab05.entities.ReservationData reservationData) {
    ro.pub.cs.aipi.lab05.reservation.ReservationInformation result = new ro.pub.cs.aipi.lab05.reservation.ReservationInformation();
    // TO DO
    return result;
}

4. [40 puncte] Să se implementeze metoda getAvailableSeats() din cadrul clasei ReservationServiceImplementation.

5. [10 puncte] Să se implementeze metoda makeReservation() din cadrul clasei ReservationServiceImplementation.

6. [10 puncte] Să se implementeze metoda cancelReservation() din cadrul clasei ReservationServiceImplementation.

7. [10 puncte] Să se apeleze metodele makeReservation() şi cancelReservation() din cadrul clasei Client. Informaţiile utilizate ca parametrii ai metodelor se citesc de la tastatură sau pot fi definite sub formă de constante în codul sursă.

8. [10 puncte] Verificaţi comportamentul la distanţă al aplicaţiei, apelând metodele implementate de colegii voştri.

Pentru ca serverul implementat de colegii voştri să fie vizibil către clientul cu care doriţi să îl accesaţi, componentele trebuie să aibă adrese IP publice sau să se găsească în aceeaşi reţea de calculatoare în cadrul căreia să aibă adrese IP private din acelaşi spaţiu (mască de reţea / gateway).
Acest exerciţiu nu va fi punctat decât în cadrul laboratorului.

9. [20 puncte] Să se definească în interfaţa IDL o metodă

Reservations getReservation(in long customerId) 

care să întoarcă lista cu intervalele în care clientul respectiv are făcute rezervări, precum şi numărul de locuri rezervate în fiecare dintre cazuri.

Să se implementeze metoda în clasa ReservationServiceImplementations.

În interfaţa IDL, o posibilă definiţie a structurii Reservations ar putea fi:

struct ReservationInformation
{
    long customerId;
    Interval interval;
    long numberOfSeats;
};
 
typedef sequence<ReservationInformation> Reservations;
Ulterior definirii metodei în interfața IDL, vor trebui generate din nou clasele ciot și schelet pentru client, respectiv server (se vor urmări etapele de la punctul 2).

Resurse

Soluții

laboratoare/laborator05.txt · Last modified: 2014/12/02 15:23 by Andrei Roșu-Cojocaru
CC Attribution-Share Alike 4.0 International
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0