MokaByte Numero  46  - Novembre 2000
Corso EJB
I servizi di sistema
di 
Giovanni Puliti
L'incredibile semplicità con cui un componente EJB risolve alcuni degli aspetti più importanti della programmazione distribuita è dovuta al supporto offerto dal server

Inizia così con questo numero una trattazione teorica del mondo EJB, trattazione che occuperà diversi numeri, e che di fatto può essere considerata il proseguimento dell’articolo introduttivo pubblicato nel numero di Maggio 2000 di MokaByte

Introduzione
Enterprise Java Beans è una tecnologia che molto spesso ultimamente viene considerata il non plus ultra dello stato dell’arte in merito a programmazione distribuita. Anche se  buona parte di tale fama è da imputarsi alla moda del momento, EJB è effettivamente una tecnologia estremamente interessante e potente. 
Già in passato abbiamo avuto modo di parlare di EJB sulle pagine di MokaByte, ed adesso è arrivato il momento di intraprendere un viaggio completo ed esaustivo su questa  interessantissima tecnologia. Questo articolo rappresenta quindi una puntata numero 2 della serie dedicata ad EJB, considerando l’articolo pubblicato in [1] come la prima parte del corso:  molti degli aspetti alla base di EJB per questo motivo verranno considerati ormai noti ed assimilati da parte del lettore. 
Per chi che volesse invece approfondire gli argomenti alla base necessari per comprendere al meglio quello che diremo qui e nelle puntate successive, si consiglia la lettura degli articoli riportati nella bibliografia al termine di questo articolo.
Infatti, grazia alla eleganza con cui è stato implementato, la complessità alla base della specifica EJB non appare agli occhi del programmatore finale, il quale la utilizza in modo trasparente, potendo così occuparsi completamente alla scrittura e progettazione della cosiddetta business logic. Sicuramente però è errato pensare di poter ignorare concetti come gestione delle transazioni, sicurezza o accesso concorrente, solo perché la specifica EJB demanda ai  costruttori dei vari Application Server la gestione di questi aspetti..
Dopo questa doverosa precisazione passiamo a introdurre quello che sarà l’argomento su cui si concentrerà l’attenzione in questa puntata: questo mese parleremo dei servizi di base che un Application Server deve implementare al fine di supportare l’utilizzo di  componente EJB di vivere in tale ambiente.
Dalle prossime puntate passeremo quindi a analizzare gli aspetti più propriamente legati alla fase implementativa, prendendo in esame alcuni esempi dimostrativi.
 
 
 

Resource Management
Fra  tutti i paradigmi di programmazione distribuita, quello basato sul Component Transaction Model (CTM, che rappresenta il modello su cui anche EJB si basa) offre alcuni innegabili vantaggi, primo fra tutti l’elevato livello di prestazioni raggiungibile in rapporto al carico di lavoro, ovvero in funzione del numero di client che accedono ai cosiddetti oggetti remoti messi a disposizione dal server.
Per questo motivo il primo aspetto che infatti si andrà a considerare è la politica messa in atto da un CTM (e quindi anche da un server di EJB) nella gestione delle risorse condivise.
Il punto basilare da tener presente è che vi è una relazione precisa fra il numero di client connessi  ed il numero di oggetti remoti che il server mette a disposizione per poter servire tali client. E’ quindi ovvio che tanto migliore sarà l’ottimizzazione della gestione e del numero di vari oggetti in esecuzione contemporanea, tanto migliore saranno le prestazioni complessive del sistema.

Proprio per il legame stretto che sussiste fra client ed oggetto remoto, un meccanismo utilizzato  e preso in prestito dai sistemi di gestione di basi di dati, è quello del pooling delle risorse. 
Alla base di tale assunzione vi è la considerazione secondo la quale molto raramente si verifica l’ipotesi per cui tutti i client dovranno accedere ad un numero prefissato di oggetti remoti nello stesso istante: quindi molto raramente sarà necessario istanziare e referenziare nello stesso istante tutti gli oggetti remoti che corrispondono ai molti client in funzione.
Questo trucco permette di ridurre il numero di connessioni aperte (come nel caso dei database) ma anche degli oggetti attivi. Inoltre il meccanismo del pool di oggetti remoti permette di riutilizzare le stesse connessioni e gli stessi oggetti remoti, visto che questo è sicuramente un processo meno costoso rispetto alla creazione/distruzione sia di connessioni che di oggetti remoti. 
Più precisamente, sebbene il server associ ad ogni client una interfaccia, interfaccia che per poter soddisfare le richieste del client deve essere associata ad un oggetto istanziato (ovvero il bean), concretamente solo pochi oggetti remoti verranno realmente istanziati ed utilizzati, dando vita ad una politica di condivisione dei pochi bean con le molte interfacce istanziate.

Questo disaccoppiamento fra quello che realmente è in funzione sul lato server, e quello che invece il client maneggia è reso possibile proprio perché quest’ultimo non accede mai direttamente agli oggetti remoti, ma sempre tramite interfacce remote, secondo lo standard di EJB.
 
 
 

Pooling di entity beans
Per comprendere come viene attuato il sistema di pooling delle risorse in EJB, si prenda in esame per prima cosa il caso degli entity bean. Un bean di questo tipo, durante il suo ciclo di vita si può trovare in uno dei seguenti stati: 

  • No state: è lo stato iniziale del bean, quando non è stato instanziato e quando nessuna risorsa è stata ancora allocata per quel componente.
  • Pooled state:  il componente è stato istanziato dal container, ma ancora non ha nessun EJB Object associato.
  • Ready State: infine il bean può essere ready, ovvero è stato istanziato ed associato ad un EJB Object, quindi pronto per l’invocazione da parte di un client.
Figura 1 - Ciclo di vista di un componente EJB nell'ambito del server

Si ricordi che l’EJB Object rappresenta una specie di wrapper del componente remoto vero e proprio. 
Sulla base di tale organizzazione risulta intuitivo come sia gestito il pooling dei componenti: il server infatti istanzia una serie di componenti remoti, memorizzandoli nel cosiddetto pool, ed associando un  componente remoto ad un determinato EJB Object  solo al momento dell’effettivo bisogno. 
Tutti i componenti remoti sono quindi equivalenti fra loro mentre sono nello stato pooled, ed acquistano una contestualizzazione solo al momento della invocazione da parte del client di uno dei business methods del bean: il client quindi effettua una chiamata ai metodi del wrapper (EJBObject) e non direttamente dell’oggetto remoto. 
Nella fase di ready il componente riceve le invocazione in callback  dal server e non dal client.

Appena un componente viene istanziato e posizionato nel pool, riceve un nuovo context appositamente creato per lui: tale oggetto, istanza a sua volta di javax.ejb.EJBContext fornisce di fatto una interfaccia al bean per comunicare con il cosiddetto EJB environment, e diventa particolarmente utile nel momento in cui il bean assume lo stato di ready.
In tale situazione infatti l’EJBContext è in grado di fornire informazioni sul client che ha effettuato le invocazioni, ma anche di fornire un riferimento alle interfacce EJB Home ed EJB Object in modo da permettere l’invocazione da parte del bean stesso dei business methods di altri componenti.

Nel momento in cui il componente non è più necessario al client, sia per una volontaria invocazione dei metodi di rimozione da parte del client, oppure perché il componente è uscito dallo scope di esecuzione (e quindi quando entra in funzione il garbage collector), allora il bean viene disaccoppiato dall’ EJBObject e successivamente riposizionato nel pool.
Può anche accadere che un bean sia reimmesso nel pool  dopo un periodo di inutilizzo prolungato da parte del client: in tal caso, nel momento in cui il client dovesse richiedere nuovamente l’utilizzo del bean, allora verrà effettuata istantaneamente una manovra di riassegnazione prelevando dal pool il primo componente disponibile.
Questa operazione prende il nome di Instance Swapping, ed è particolarmente importante, dato che permette di servire un alto numero di richieste utilizzando poche risorse attive, ovvero poche istanze di componenti remoti: il tempo necessario per svolgere le operazioni relative ad una invocazione è sicuramente minore del tempo passato inattivo. 
Quando un componente ha quindi terminato di servire un  determinato client, può essere riutilizzato per servirne un altro. Complessivamente ogni client ha quindi l’illusione che ci sia un oggetto remoto per lui, mentre invece una politica efficiente di condivisione permette una drastica riduzione delle risorse e del tempo computazionale.
 
 
 

Pooling di session beans
Nel caso degli stateless il meccanismo è semplificato dalla natura stessa del componente: infatti in questo caso non viene mantenuta nessuna informazione  fra due invocazioni dei metodi da parte del client, e quindi non è necessario nessun meccanismo di memorizzazione dello stato.  Ogni metodo esegue le istruzioni senza che si debba accedere in qualche modo ad informazioni memorizzate da qualche parte.
Questo permette quindi al container di effettuare il processo di swapping senza tener conto di quale particolare istanza venga utilizzata.
Nel caso degli stateful session beans invece le cose sono leggermente differenti: in questo caso l’integrità delle informazioni deve essere mantenuta in modo da ricostruire in ogni istante la storia delle varie invocazioni (che si definisce conversational state, ovvero stato della conversazione).
Per questo motivo per gli stateful  beans non viene utilizzato il meccanismo di swapping di contesto, come invece accade per gli stateless e per gli entity. 
In questo caso tutte le volte in cui sia necessario utilizzare un componente, è sufficiente prelevarne uno vuoto dalla memoria (quindi in modo simile al sistema di pool), ripristinandone lo stato prelevando le informazioni direttamente da un sistema di memorizzazione secondaria (tipicamente file serializzati su file system). Questa operazione viene detta attivazione, ed è praticamente la simmetrica rispetto a quella di scrittura su disco che prende il nome di passivazione. 
Un aspetto piuttosto importante è quello legato agli eventuali campi definiti come transient: il meccanismo di salvataggio e ripristino dello stato si basa  infatti sulla serializzazione, la quale, come noto, impone il ripristino ai valori di default per i campi transient (per gli interi ad esempio il valore 0, per i reference null, per i boolean false e così via) nel momento della deserializzazione dell’oggetto. 
In questo caso però non si può avere la certezza matematica dello stato dell’oggetto al suo ripristino, visto che la specifica EJB 1.0 lascia libertà di scelta all’implementatore del server. 
Per questo motivo, dato che l’utilizzo dei campi transient non è necessario tranne che in rare occasioni, se ne sconsiglia l’utilizzo.

Anche in questo caso  il client è del tutto all’oscuro del processo di attivazione/passivazione, proprio perché si utilizza un wrapper per accedere all’oggetto remoto.
Si ricorda che i metodi di callback utilizzati dal server informare degli eventi di attivazione e passivazione sono i due ejbActivate() ed ejbPassivate(): il primo viene invocato immediatamente dopo l’attivazione, ed il secondo immediatamente prima la passivazione del componente. 
Entrambi sono particolarmente utili in tutti quei casi in cui si rende necessaria una gestione particolareggiata delle risorse esterne al server, come ad esempio se il componente deve utilizzare una connessione con un database o con un qualsiasi altro sistema di comunicazione.
Infine nel caso degli entity beans non si parla di mantenimento dello stato, ma piuttosto di persistenza vera e propria di tutte le informazioni memorizzate nel componente: in questo  caso infatti i dati vengono memorizzati in un database non appena una qualche modifica viene effettuata nel componente. 
Un entity bean  si appoggia comunque ai metodi di callback ejbActivate() ed ejbPassivate()al fine di svolgere al meglio le operazioni in ogni istante del proprio ciclo di vita.
 
 
 

Servizi primari di sistema 
Una tipica struttura basata sul paradigma della programmazione distribuita poggia buona parte della propria architettura sulla possibilità di utilizzare una serie di servizi di base, che finiscono per costituire il motore di tutto il sistema.
La specifica ufficiale CORBA di OMG ad esempio definisce 13 servizi principali, anche se la maggior parte dei vari ORB commerciali e dei sistemi CTM ne implementano solo alcuni.
Nel caso di EJB, la specifica dice che tali servizi sono a carico del server, lasciando il programmatore del componente il solo compito di concentrarsi sullo sviluppo della parte di business logic del componente.
In questo caso si analizzeranno solo i servizi primari che sono alla base di un sistema CTM completo e funzionale. Tali servizi prevedono la gestione dei seguenti aspetti: concorrenza, transazioni, persistenza, oggetti distribuiti, naming e security.
 
 
 

Concorrenza 
Quello della concorrenza è questo un problema molto importante, ma che per fortuna nel caso dei session beans non si pone, grazie alla natura stessa di questi componenti.
Ogni componente stateful infatti corrisponde ad uno ed un solo client, che può invocarne i metodi da remoto. In questo caso non ha senso pensare ad uno scenario concorrente, dato che di fatto il soggetto invocante risulta essere sempre al massimo uno.
Per gli stateless a maggior ragione l’accesso concorrente perde ancor più di significato, dato che questo tipo di oggetti non memorizzano nessuna informazione conversazionale, e quindi mai due soggetti potranno interferire fra loro. Inoltre lo scope delle variabili coinvolte in una invocazione di un metodo sono per forza di cosa limitate allo scope dell’invocazione stessa.
Nel caso degli entity invece il problema si pone ed in tutta la sua drammaticità: un entity infatti rappresenta un dato non legato ad un particolare client, dando luogo ad un tipico scenario di accesso concorrente.
La soluzione adottata in questo caso è la più conservativa, limitando l’accesso concorrente non solo a livello di risorsa specifica, ma più precisamente di thread di esecuzione.  Per thread di esecuzione si può intendere ad esempio una serie di soggetti in esecuzione conseguente (A invoca B che  invoca C).
 
 
 

Figura 2a - L'invocazione rientrante di un metodo può dar vita a problemi di deadlock

Questo fatto introduce una serie di importantissime conseguenze, fra cui la più immediata è che, risulta essere proibito l’utilizzo dello statement synchronized: infatti, anche se il meccanismo di funzionamento attuato dal  server di base è lo stesso,  è importante lasciare al server la gestione degli accessi concorrenti. 
Una volta di più il programmatore si deve semplicemente occupare dello sviluppo della sola business logic. 
Questa politica, pur limitando notevolmente l’insorgere di possibili problemi, lascia scoperto il caso delle cosiddette chiamate  rientranti o loopback calls. 
Si immagini ad esempio il caso in cui un client invochi un metodo di un bean A, il quale a sua volta necessiti di invocare un metodo di un oggetto B. In questo caso il thread di esecuzione facente capo al client  acquisisce il lock associato al componente A. Nel caso in cui il metodo di B debba  a sua volta invocare un metodo di A, si otterrà un blocco molto simile al cosiddetto deadlock: infatti B attende lo sblocco del lock su A per poter proseguire, evento che non di potrà verificare. 
Questo scenario nasce dalla politica conservativa di attivare il lock a livello di thread di esecuzione, piuttosto che singole entità.
La soluzione a questo problema è quello di proibire il loopback: per i session la sua presenza porta ad una RemoteException, mentre per gli entity possono essere configurati per funzionare anche in situazioni di loopback, anche se di fatto è una casistica fortemente scoraggiata.
 
 
 

Figura 2b - La gestione dei lock a thread di esecuzione evita l'insorgere di lock bloccanti

Si tenga presente però che un componente può tranquillamente accedere ai suoi metodi (ovvero un metodo di A può invocare senza problemi un altro metodo di A) senza dover sottostare ai vincoli di lock o simili, come del resto avviene per i thread classici di Java.

Prima di procedere si consideri che ogni invocazione di un metodo di un bean avviene sempre e comunque secondo lo schema classico della invocazione remota basata su EJB Object. Quindi nell’esempio appena visto, il client C invoca i metodi di A secondo lo schema classico, ma equivalentemente  A diviene client di B, e quindi invoca i suoi metodi ricavandone l’interfaccia remota. B a sua volta nella chiamata di loopback ritorna ad essere client di A, e quindi, grazie al meccanismo di lock sui thread non può eseguire tale operazione.
 
 

Figura 3 - L'utilizzo di un componente, anche se locale, avviene sempre tramite il meccanismo dell'invocazione remota

Anche se questo scenario può apparire eccessivamente complesso, presenta due importanti vantaggi: da un lato mantiene un elevato livello di standardizzazione, dato che ogni componente deve essere progettato ed implementato seguendo una rigida specifica standard. Dall’altro, visto che A invoca i metodi di B tramite una interfaccia remota, è possibile effettuare sostituzioni, e migrazioni del bean da un server ad un altro, senza che il client ne abbia percezione. Questo permette massima flessibilità, semplicità di gestione e con poco lavoro consente l’implementazione di tecniche di load balancing fra server differenti.
Questa scelta architetturale   è però forse  anche il principale tallone d’Achille di tutta l’architettura EJB, dato che può provocare eccessivi rallentamenti in fase di esecuzione: infatti ogni invocazione è sempre vista come una invocazione remota alla RMI anche se l’oggetto è in esecuzione localmente (come potrebbe essere nel caso di B).
 
 
 

Le transazioni
Il modello di EJB fornisce un robusto supporto per le transazioni. Anche in  questo caso il programmatore del componente non deve implementare nessuna struttura particolare per fornire il supporto alla transazioni, dato che è sufficiente definire tale funzionalità al momento del deploy del componente: indicando, ad esempio tramite file XML, quali operazioni dovranno essere atomiche, e quindi specificando un contenitore di transazioni, il server si occuperà di effettuare tutto il resto.
 
 
 

Gestione della persistenza 
La gestione della persistenza delle informazioni è un aspetto molto importante, anche se riguarda esclusivamente il caso degli entity bean. Questi oggetti di fatti rappresentano un particolare dato sul server, e quindi possono essere memorizzati tramite l’utilizzo di un database di vario tipo.
Tale sincronizzazione con la memoria secondaria, permette, fra le altre cose, la possibilità di implementare applicazioni robuste e fault tolerant:  ad esempio in seguito ad un blocco del sistema, ogni componente riassumerà la sua originaria configurazione semplicemente rileggendo i dati memorizzati nel database.
La gestione della persistenza può essere effettuata in due modi: container managed o bean managed. 
Nel primo caso è il container  che si preoccupa di effettuare il mapping e la sincronizzazione fra i dati contenuti nel componente, e quelli memorizzati nel database.
Nel secondo caso invece, sempre appoggiandosi ad un archivio esterno, tutta la logica di sincronizzazione dei dati deve essere implementata a mano.
Ovviamente la prima soluzione è quella più comoda e sicura, mentre la bean managed si utilizza in alcune particolari circostanze: ad esempio ove necessaria  una personalizzazione del processo di salvataggio dei dati, oppure nel caso in cui ci si debba interfacciare con del codice legacy. 
Quest’ultimo in particolare è un caso piuttosto tipico: si immagini infatti il caso in cui si voglia mettere in comunicazione uno o più sistemi legaci. 
Molto spesso si ricorre alla progettazione di una architettura che vede la presenza di componenti remoti funzionanti come contenitori, i quali da un lato si mettono in comunicazione con il legacy in un qualche modo (ad esempio via socket o per mezzo di JNI), e dall’altro esportano una interfaccia EJB; questo permette l’invocazione da pare del mondo Java dei servizi o delle applicazioni native incapsulate all’interno dell’oggetto remoto.
Il legame fra bean e dati memorizzati nel database è talmente stretto che una modifica nel primo si riflette in un aggiornamento dei dati, mentre viceversa inserire a mano un nuovo record (ad esempio tramite uno statement SQL se si tratta di database relazionale) corrisponde ad creare un nuovo bean, anche se non necessariamente tale operazione produce una istanziazione di nuove classi Java.
 
 
 

Mapping dei dati e modello di memorizzazione
Nel momento in cui si deve scegliere il tipo di database da utilizzare per la memorizzazione di un entity, due sono possibili le soluzioni: database relazionali oppure ad oggetti.
Il modello relazionale è quello adottato nella maggior parte dei casi a causa delle maggiori prestazioni, della maggiore affidabilità e maturità dei prodotti rispetto ad altre tecnologie,  e soprattutto perché è più conosciuto dagli utilizzatori finali. 
La grossa limitazione di questo modello è quello di essere estraneo alla logica ad oggetti utilizzata in EJB ed in Java in genere: per questo motivo che si rende necessario uno strato di software con cui effettuare il mapping relazionale-object oriented, in modo da trasformare i campi di un bean in valori delle colonne di un database. 
Purtroppo non sempre è possibile effettuare in modo lineare ed automatico il passaggio da modello relazionale  ad oggetti e viceversa, tanto che in molti casi si rendono necessarie conversioni manuali (come ad esempio nel caso in cui particolari oggetti Java non siano direttamente convertibili nel formato accettato dal database). 
Inoltre la struttura del relazionale può complicarsi molto in funzione alla reale struttura del bean di partenza.
I database ad oggetti invece  non richiedono nessuna operazione di conversione, e quindi permettono ad un componente di essere salvato in modo praticamente diretto nel database stesso, consentendo di limitare la complessità della struttura.
Lo svantaggio di questo tipo di prodotti è che si tratta di una tecnologia ancora non matura, dalle prestazioni non paragonabili con il relazionale, e che inoltre non è ancora molto nota alla massa.
 
 
 

Servizio di Naming 
Qualsiasi sistema di programmazione distribuita ha alla base un qualche meccanismo che permetta di rintracciare oggetti remoti in esecuzione in spazi di indirizzamento eterogenei. 
Un sistema di questo tipo si basa sui  cosiddetti  naming services che sono essenzialmente di due tipi: servizio di bind (ovvero registrazione di un oggetto remoto in un qualche registry tramite nome logico) e servizio di lookup (che corrisponde alla operazione inversa, ovvero ricercare la particolare istanza di un oggetto remoto, partendo da un nome logico).
Convenzionalmente si associa tutto alla lookup,  definendo un set di API tramite le quali il client è in grado di risalire all’oggetto remoto.
Nel caso di EJB il meccanismo utilizzato è quello offerto dalla  Java Naming and Directory Interface (JNDI API).  Questo strumento implementa una astrazione gerarchica ad alto livello di una ipotetica collezione di risorse (file, oggetti, stampanti, devices vari). Per una trattazione approfondita del funzionamento di JNDI si può far riferimento alle risorse riportate in bibliografia, ed in particolare [7]. 
JNDI  non rappresenta l’unico strumento di naming utilizzabile all’interno di un server EJB, anche se la specifica dice che ogni produttore di server deve fornire almeno una interfaccia di questo tipo ai vari client che intendano utilizzare i bean installati.
Da questo punto di vista il  client deve utilizzare la JNDI API per iniziare la connessione verso un EJB Server specificando l’interfaccia EJB Home dell’oggetto remoto che si intende utilizzare. Ad esempio si potrebbe scrivere

import javax.naming.*;

Context jndiContext = new  InitialContext(props);
MyHome home = (MyHome) jndiContext.lookup(beanName);
MyBean  myBean =Home.create(param1, param2);
myBean.doSomething();

In questo caso props  istanza di un oggetto Properties, indica al servizio JNDI dove si trova il server EJB da utilizzare; beanName invece indica il nome logico con cui il componente è stato registrato. 
 
 
 

Security 
La gestione della sicurezza è uno degli aspetti chiave su cui si basano la maggior parte dei sistemi di comunicazione e di programmazione distribuita moderni.
Si può intendere la sicurezza a tre  livelli: autenticazione, access control e sicurezza nella comunicazione.
Nel primo caso, grazie ad un qualche sistema di riconoscimento si permette ad un utente (persona fisica) o entità (ad esempio un oggetto) di entrare nel sistema e di usufruire dei servizi messi a disposizione.  Questo sistema in genere è considerato non sufficientemente flessibile, dato che permette di attivare o  disattivare del tutto il set delle possibili operazioni effettuabili. Per questo spesso si parla di meccanismo On/Off. Il riconoscimento può avvenire tramite password, certificato digitale o tramite meccanismi particolari come smart card o devices elettromagnetici.
Anche se in EJB non è formalmente specificato nessun meccanismo per permettere l’autenticazione, grazie ai servizi offerti da JNDI, si può comunque implementare un qualche sistema di riconoscimento. 
Ad esempio si possono utilizzare le informazioni per il login al momento in cui si effettua l’operazione di lookup semplicemente passando i dati utente come proprietà al context che effettua la ricerca; di seguito ecco un breve esempio

import javax.naming.*;
... 
Properties props = new Properties();
props.put(Context.SECURITY_PRINCIPAL, uid);
props.put(Context.SECURITY_CREDENTIALS, passwd);
Context jndiContext = new  InitialContext(props);
... 

Nel caso si possa disporre di un sistema di access control, allora è possibile definire una policy di permessi, in modo da autorizzare un utente ad effettuare determinate operazioni.
Questa tipologia di security è quella incluso nella specifica 1.0 di EJB.
In questo caso ad ogni client viene associato un elemento di istanza di java.security.Identity che rappresenta l’identità con cui il client potrà effettuare le operazioni con gli oggetti remoti. Una Identity rappresenta un utente o un ruolo nel caso in cui l’oggetto remoto sia invocato da un altro bean, che come specificato in precedenza assume il ruolo di client. Questa puntualizzazione è molto importante, dato che colma una carenza presente molto spesso in sistemi distribuiti: ad esempio ci si potrebbe chiedere nel caso di un servlet, con quali diritti esso sia mandato in esecuzione (generalmente con quelli dell’utente che ha fatto partire il servlet container, ma non è detto).
La Identity associata ad un client viene utilizzata in modo del tutto trasparente: ad esempio quando il client richiede l’utilizzo di un particolare metodo, alla invocazione dello stesso viene passata anche l’istanza della identità associata, al fine di effettuare il controllo sui diritti.
La definizione delle varie policy si effettua al momento del deploy grazie al DeploymentDescriptor il quale contiene  i vari ControlDescriptors ed AccessControlEntrys: questi oggetti permettono di definire la lista degli utenti ammessi ad effettuare determinate azioni, mentre i ControlDescriptors consentono fra l’altro di specificare il “runAs” di ogni identity, ovvero con quale Identity ogni metodo potrà essere eseguito. 
Ad esempio si può specificare che un metodo qualsiasi di un determinato componente può essere eseguito solo dall’utente “pippo”, ma che tale metodo poi verrà eseguito nel sistema come se si trattasse dell’utente “amministratore di sistema”.
Si intuisce quindi come tutti questi meccanismi possano garantire un altissimo livello di personalizzabilità del sistema in fatto di sicurezza. 
Il prezzo da pagare è ovviamente la complessità del tutto, come già in passato è avvenuto con l’introduzione dei sistemi avanzati di crittografia in Java, o con l’avvento del processo di firma digitale delle applet.

Infine si può intendere la sicurezza come un meccanismo per rendere le comunicazioni (in EJB l’invocazione dei metodi ed i parametri passati) non decifrabili da estranei.
Quasi sempre questo si traduce nel dover utilizzare sistemi di crittatura, chiavi asimmetriche e simili.  Al momento nella specifica EJB non è stata formalmente  definita nessuna tecnica per supportare meccanismi di questo tipo.
 
 
 

Conclusioni
Termina qui questa panoramica teorica sulle  funzionalità di base che un server EJB deve mettere a disposizione. Sicuramente si sarà potuto evidenziare la mancanza di esempi o approfondimenti sulle singole implementazioni: lo scopo di questo articolo infatti non è tanto quello di dettagliare il funzionamento interno dei vari server, ma piuttosto evidenziare come sia possibile utilizzare in modo del tutto automatico e trasparente alcune funzionalità importantissime di questo genere di oggetti.
In futuro invece torneremo a proporre approfondimenti concreti su come realizzare componenti EJB e sfruttare le funzionalità avanzate messe a disposizione dei vari prodotti commerciali.
 
 
 

Bibliografia
[1] “Enterprise Java Beans: una panoramica introduttiva” di Giovanni Puliti – MokaByte 41 maggio 2000, www.mokabyte.it/2000/05/ejb.htm
[2] Networking in Java   V puntata: RMI  - MokaByte  43 - Luglio Agosto 2000 - www.mokabyte.it/2000/07/reti5_teoria.htm
[4] RMI e IIOP - MokaByte Numero  44  - Settembre 2000 - e successivi - www.mokabyte.it/2000/09/rmiiop1.htm
[5] Programmazione per pattern: MokaBte 23 - Ottobre 1998
- www.mokabyte.it/1998/10/pattern.htm
[6] “JNDI” di D’amore Salvatore MokaByte 41 – Maggio 2000, www.mokabyte.it/2000/05/jndi1.htm e successivi

Chi volesse mettersi in contatto con la redazione può farlo scrivendo a mokainfo@mokabyte.it