Acquisizione Dati Tramite RS-232 del Decoder del

UNIVERSITA’ POLITECNICA DELLE MARCHE
FACOLTA’ DI INGEGNERIA
Corso di Laurea in Ingegneria Informatica e dell’Automazione
Acquisizione Dati Tramite RS-232 del Decoder del Digitale
Terrestre ed Invio Tramite Lo Stack TCP/IP
Relatore
Tesi di Laurea di
Prof. Aldo Franco Dragoni
Riccardo Biancucci
_______________________
_________________
Anno Accademico
2007 / 2008
Indice
1 - Introduzione …………………………………………………………. pag. 3
2 - MHP, Una Breve Panoramica ……………………………………….. “
5
2.1 - Il Contesto: La Televisione Digitale Terrestre ………………………. “
5
2.2 - DVB-T: Elementi Correlati allo Sviluppo di Applicazioni MHP …… “
8
2.3 - Il Middleware MHP …………………………………………………. “
12
3 - MHP, Alcune API e Funzionalità di Specifico Interesse ………….. “
22
3.1 - org.dvb.test.DVBTest ……………………………………………….. “
22
3.2 - java.lang.Runtime …………………………………………………… “ 23
3.3 - Broadcast Filesystem ………………………………………………... “
24
3.4 - Memorizzazione Persistente ………………………………………… “ 27
3.5 - Return Channel ……………………………………………………… “
28
4 - Considerazioni Operative e Qualche “Proof of Concept” ………… “ 31
4.1- Acquisizione Dati da Interfaccia RS-232 ……………………………. “ 31
4.2 - Accesso al Filesystem Persistente …………………………………... “ 34
4.3 - Classe DataRetriever ………………………………………………... “ 41
4.3.1 - Interfaccia DataRetrieverInterface ………………………………... “ 45
4.3.2 - Classe DataRetrieverOsmosys…………………………………….. “ 46
4.4 - Connettersi ad un ISP ……………………………………………….. “ 49
4.4.1 - Classe NetConnection …………………………………………….. “ 49
1
4.5 - Invio dei Dati ad Un Sistema Remoto Tramite TCP/IP …………… pag. 52
4.5.1 - Classe TCPClient …………………………………………………. “ 53
4.5.2 - Classe SocketIOUtil ………………………………………………. “ 61
4.6 - Un Server per La Ricezione dei Dati Inviati ………………………... “ 66
4.6.1 - Classe TCPServer …………………………………………………. “ 62
4.6.2 - Classe TCPServerTest …………………………………………….. “ 71
4.6.3 - Classe SocketIOUtil in TCPServer …………………….…………. “ 72
4.7 - Accedere ad Una Risorsa nell’Object Carousel …………………….. “ 72
4.7.1 - Un File di Configurazione ………………………………………… “ 72
4.7.2 - Classe ConfigFileParser …………………………………………... “ 73
4.8 - Una Xlet Minimale ………………………………………………….. “ 77
4.8.1 - Classe RS232toTCP ………………………………………………. “ 77
4.9 - Conclusioni ………………………………………………………….. “ 83
5 - Fonti di Documentazione in Formato Elettronico e Bibliografia …. “ 84
2
1 - Introduzione
Alcune caratteristiche della piattaforma DVB-MHP (Digital Video Broadcasting
– Multimedia Home Platform) per la televisione digitale terrestre, quali l’elevato
grado di programmabilità tramite un linguaggio di programmazione completo e
di uso diffuso come Java e tramite una parte delle API standard in tale
linguaggio, la disponibilità del cosiddetto “return channel” che permette di
sfruttare, almeno in parte, le potenzialità di Internet, il costituire, almeno
nell’area europea, uno standard sia di fatto che, per gran parte, anche formale,
rendono un Set Top Box (o STB per brevità) conforme alle specifiche MHP un
terminale adatto alla fruizione, oltre che dei tipici contenuti televisivi, di una
vasta gamma di servizi, servizi non necessariamente appartenenti o correlati
all’ambito dell’intrattenimento.
Inoltre i fattori tipici del nuovo sistema televisivo e della citata piattaforma
tecnologica su cui esso è imperniato, che hanno reso e stanno rendendo (a
differenza di altri sistemi televisivi digitali, anch’essi potenzialmente interattivi,
ad esempio la tv satellitare) la relativa utenza sempre più di massa, costituiscono
un ulteriore incentivo a considerare la televisione digitale terreste ed i STB MHP
(o dispositivi equivalenti, ormai il relativo hardware e software è integrato
spesso nei televisori) mezzi idonei anche per l’erogazione di servizi.
Ulteriore punto a favore, nell’ottica menzionata, è costituito dalla semplicità
intrinseca dei metodi di interazione, tramite telecomando; paradigma ormai
consolidato da decenni praticamente presso la totalità della popolazione ed
ereditato anche nell’uso di un terminale MHP; unitamente al fatto che per
esempio un STB è un mezzo a basso costo (o relativamente a basso costo)
rispetto ad altri adatti alla fruizione di servizi telematici (tipicamente, un PC),
ciò potrebbe rendere più agevole raggiungere con un dato servizio anche quelle
categorie di utenza tradizionalmente più “refrattarie” alla tecnologia o meno
propense ad “investire” in tale ambito, come ad esempio una consistente parte
delle popolazione al di sopra di una certa età.
Con un occhio di riguardo proprio a queste ultime considerazioni, una delle
tipologie di servizio ipotizzabili, non correlate all’ambito del tradizionale
contenuto televisivo o all’intrattenimento in generale, potrebbe essere quella che
preveda di utilizzare un terminale MHP come una sorta di “ponte” per inviare a
ad un sistema remoto dei dati acquisiti da un dispositivo ad esso collegato,
magari dati di tipo biometrico, nell’ottica di un sistema di tele-monitoraggio in
ambito medico, ad esempio.
3
E’ infatti possibile reperire STB che dispongano di una interfaccia di
connessione conforme allo standard EIA RS-232 (una comune “porta seriale”,
tipicamente); ad esempio alcuni dispositivi di comune uso in ambito medico,
quali alcune macchine per l’ ECG, dispongono della medesima interfaccia, il
che rende questo tipo di connessione attualmente la principale candidata per gli
scopi ipotizzati.
Il presente elaborato si pone come uno studio preliminare atto ad analizzare
metodi, tecnologie e problematiche correlate nell’ottica di poter utilizzare un
terminale MHP, tramite una opportuna applicazione, per acquisire dati
attraverso l’interfaccia seriale e poi inviarli ad un sistema remoto, sfruttando
l’implementazione del “return channel” (tramite connessione ad un ISP, volendo
veicolare i dati attraverso Internet); in particolare, l’analisi sarà rivolta alle
funzionalità ed alle interfacce di programmazione potenzialmente utili
all’obiettivo descritto, tra quelle messe a disposizione dal middleware MHP.
Da qui in avanti, riferendosi alla piattaforma MHP, alla relativa specifica ed API
in essa incluse, ove non diversamente esplicitato, si intenderà riferirsi alla
versione 1.0.2: si opta per il “minimo comun denominatore” dato che non vi è
ancora una diffusione significativa di dispositivi implementanti il ramo 1.1 della
specifica e che con tipologie di servizi come quelle ipotizzate le caratteristiche
esclusive della specifica più recente non sarebbero essenziali; inoltre, nell’ottica
di erogare un servizio anche se di diverso tipo e più “tradizionale”,
massimizzare il bacino di utenza può essere un fattore importante.
Si consideri poi che la maggioranza dei STB in commercio di fatto implementa
MHP 1.0 ma i produttori spesso non si curano di ottenere la certificazione
relativa o semplicemente dichiarare la particolare revisione implementata; ed
ancora, anche dando tutto il peso che meriterebbero alle certificazioni, la “test
suite” che deve essere eseguita con successo da un terminale MHP per ottenerle,
è attualmente ferma alla versione 1.0.2. Ecco quindi altri due motivi per cui si
ripiega sulla specifica 1.0.2, la prima tra quelle tuttora disponibili, anche rispetto
alla 1.0.3, sebbene quest’ultima, comprensiva di “errata corrige 3”, venga
indicata come quella (attualmente) “definitiva” per quel che riguarda il ramo
1.0.
4
2 - MHP, Una Breve Panoramica
Come già accennato, MHP (Multimedia Home Platform) è il nome collettivo per
un insieme di specifiche per un middleware aperto basato su Java, specifiche
definite dal DVB-Project intorno al quale si raccoglie un consorzio di importanti
aziende attive nell’ambito del video digitale e delle telecomunicazioni. Le
specifiche MHP sono definite in modo che un sistema conforme possa
funzionare con tutte le tecnologie di trasmissione DVB (chiaramente, MHP
verrà qui considerato in particolare in relazione allo standard DVB-T).
Inoltre gli standard DVB sono stati in seguito adottati anche in aree extra
europee e la loro adozione è tuttora in espansione. Da MHP è anche stato tratto
un sottoinsieme chiamato Globally Executable MHP o GEM, che non contiene
la parte relativa specificamente alle direttive DVB per quel che riguarda la
trasmissione dati, in modo da avere un middleware applicabile anche in mercati
che usino differenti formati di trasmissione, o anche in contesti non
necessariamente legati alla ricezione di contenuti ed applicazioni trasmessi in
broadcast (ad esempio GEM è stato anche adottato nella specifica del profilo di
interattività per il noto formato multimediale Blu-ray Disc) con il vantaggio di
mantenere una parziale interoperabilità.
Attualmente, sia MHP che GEM sono pubblicati come standard aperti dall’ETSI
(European Telecommunications Standards Institute)
Di seguito si danno alcuni cenni generali relativi ad MHP, iniziando dall’ambito
in cui tale middleware è più utilizzato e da alcuni elementi dello standard
DVB-T correlati al concetto di applicazione MHP, per poi analizzare
brevemente quanto propone la specifica MHP in sé, con particolare riguardo alle
interfacce di programmazione.
2.1 - Il Contesto: La Televisione Digitale Terrestre
La televisione digitale terrestre o DTT (Digital Terrestrial Television), sul punto
di soppiantare del tutto, anche in base ad un apposito quadro normativo, il
sistema di trasmissione analogico, può essere vista, nella realtà italiana, come
l’ultimo tassello che si aggiunge a quel processo che in poco più di un decennio
ha portato i principali flussi informativi alla codifica digitale.
5
I vantaggi del sistema DTT risiedono principalmente nei seguenti elementi:
- l’adozione, appunto, di un sistema di trasmissione digitale che rispetto al
classico sistema analogico presenta maggiore robustezza rispetto alle
interferenze, possibile miglior quantità del segnale trasportato (in termini di
maggiore informazione veicolata), occupazione di minore banda nell’intervallo
delle frequenze utilizzabili per la trasmissione; il tutto implica potenzialmente
una maggiore qualità visiva e maggiore quantità di canali televisivi disponibili;
- la presenza di un “canale di ritorno” bidirezionale che collegando il ricevitore
ad una rete di telecomunicazioni permetta all’utente di interagire, attraverso
opportuni stadi intermedi, con il fornitore di contenuti (emittente televisiva) e/o
con eventuali fornitori di servizi aggiuntivi/correlati (di fatto, ad oggi questa
caratteristica si traduce nell’implementare sui ricevitori DTT quanto necessario
affinché siano tecnicamente disponibili, almeno in parte, le potenzialità offerte
da Internet);
- la compatibilità del segnale, a livello fisico, con il segnale analogico: questo
implica la possibilità per l’utente di ricevere il segnale digitale con gli apparati,
in quanto ad antenne e cablaggi, pre-esistenti;
- l’adozione, sia per quel che riguarda la codifica (non con riferimento alla
contrapposizione binaria/analogica ma più in generale ed ad alto livello alle
“strutture dati” veicolate) delle informazioni trasmesse che riguardo alla
gestione dei vari aspetti dell’interattività a livello di terminale dell’utente, di
tecnologie che, quantomeno nell’area europea, costituiscono ad oggi uno
standard di fatto, nonché anche formale in buona parte, in quanto molto di
quanto messo a punto dal consorzio DVB (Digital Video Broadcasting) per i
sistemi DVB-T (DVB-Terrestrial) ed MHP è stato ratificato dall’ ETSI
(European Telecommunications Standards Institute).
Gli ultimi due punti costituisco una forte base per un sempre maggiore
allargamento dell’utenza del sistema DTT (il che, in realtà, nel nostro paese, è
anche un fatto direttamente implicato dal quadro normativo in essere) ed anche
per una sempre maggiore fruizione della cosiddetta “iTV” (interactive TV) ,
probabilmente più di quanto sia avvenuto sino ad oggi nel quadro per esempio
della televisione satellitare che pur ne ha teoricamente le potenzialità tecniche;
infatti la fruizione di servizi interattivi nell’ambito del sistema DTT è facilitato
dal fatto che i fornitori di tali servizi si trovano ad operare su una piattaforma
che sostanzialmente standardizza tutti gli elementi necessari all’interoperabilità
e che “nasconde” le differenze proprie degli specifici dispositivi in possesso
dell’utenza.
6
La seguente figura mostra una rappresentazione, a livello indicativo e
macroscopico, di una tipica catena di trasmissione relativa al sistema DTT, che
vede i suoi principali elementi nel fornitore di contenuti, nell fornitore di
applicazioni (che vengono eseguite sul terminale dell’utente) per l’interattività,
in un eventuale fornitore di servizi terzo rispetto ai primi due (servizi anche non
necessariamente strettamente correlati ai tipici contenuti del servizio televisivo),
negli stadi necessari per un eventuale feedback da parte dell’utente attraverso
Internet e negli apparati per il mixaggio e la modulazione affinché sia i contenuti
audio-visuali che altri tipi di dati siano trasmessi in modo conforme alle
specifiche DVB-T; e, ovviamente, nel “terminale” in possesso dell’utente,
elemento tutt’altro che trascurabile in quanto ospita il middleware MHP,
elemento importante nell’economia complessiva di tutto il sistema.
(immagine tratta da http://www.mhp-knowledgebase.org )
7
In questa visione complessiva del sistema DTT, si può puntualizzare che,
teoricamente, non si potrebbe dare per scontata la disponibilità per tutti gli
utenti del “return cannel”, in quanto, in linea di principio, un produttore di
ricevitori per la tv digitale, potrebbe anche costruire un dispositivo conforme
alle specifiche per la gestione di un flusso dati DVB-T senza implementare il
middleware MHP (la cui specifica prevede appunto nel terminale la presenza di
hardware atto a fornire un qualche tipo di connessione remota e le relative
interfacce di programmazione per usarlo, indipendenti dal dispositivo specifico);
inoltre la stessa specifica MHP prevede un profilo (si tornerà in seguito sui
profili definiti dalla specifica MHP) di conformità che non include
obbligatoriamente il return channel.
Tuttavia, almeno nell’area europea, si tratta di un inconveniente che
fortunatamente rimane solo sulla carta in quanto i produttori di dispositivi per la
ricezione della DTT hanno sin dall’inizio della produzione di massa di tali
prodotti implementato anche le specifiche MHP, inclusive nella totalità dei casi
(quantomeno “statisticamente parlando”) della caratteristica “return channel”
(in genere, ad oggi, tramite inclusione di modem PSTN).
2.2 - DVB-T: Elementi Correlati allo Sviluppo di Applicazioni MHP
Le scelte tecnologiche che si possono operare nell’implementazione di un
sistema televisivo digitale ed interattivo sono innumerevoli, come testimonia ad
esempio la grande quantità di formati disponibili per la codifica digitale di audio
e video.
Senza uno standard che copra un’area geografica quantomeno relativantemente
vasta, sia i fruitori di contenuti e servizi che i fornitori dei medesimi, si
troverebbero in un sistema afflitto da varie difficoltà ed inefficienze e
caratterizzato con tutta probabilità da tante scelte “ugualmente sbagliate”.
Le specifiche per la TV digitale a livello europeo, con un lavoro iniziato negli
anni ’80, sono state formalizzate e ed in seguito ratificate come standard ETSI
dal consorzio DVB (Digital Video Broadcasting). Sono state definite tre
specifiche principali: DVB-S, DVB-C, DVB-T relative alla trasmissione digitale
rispettivamente satellitare, via cavo e “via etere” ma esclusivamente tramite
apparati a terra, ovvero “terrestre”, appunto.
Tali specifiche prevedono il formato MPEG-2, al momento della scelta già
ampiamente diffuso, per l’audio ed il video (per l’audio, esattamente le
specifiche “MPEG-2 layer I” e “MPEG-2 layer II” ), quello DSM-CC per il
trasporto nel medesimo segnale anche di dati di tipo non audio-visuale
(basandosi sul fatto che un “MPEG Transport Stream” o MPEG-TS è per sua
specifica adatto anche ad incapsulare contenuti aggiuntivi a quelli audio-video).
8
Infine in tempi più recenti è stata formalizzata, allo scopo di fornire strumenti
unificati principalmente per l’interattività, la specifica DVB-MHP (più
comunemente referenziata, così come nel presente testo, semplicemente come
“MHP”), con la versione più remota tra quelle ad oggi pubblicamente
disponibili, la 1.0.2, datata giugno 2001.
Lo standard DVB definisce tutte le caratteristiche che deve possedere un sistema
per la trasmissione digitale.
Per quel che riguarda il livello logico:
- formato audio/video
- regolamentazione dei flussi dati
- formato dei pacchetti
- segnalazione dei servizi
- uso di dati non audio/video e protocolli esterni
Relativamente al livello fisico, la specifica definisce le caratteristiche del
segnale nei vari mezzi fisici inerenti i diversi ambiti DVB-S/C/T
Essendo interessati all’ambito del sistema DTT, ci si focalizzerà ovviamente
sullo standard DVB-T, o meglio, come da titolo del presente paragrafo, su alcuni
specifici aspetti, spesso rilevanti nello sviluppo di una applicazione MHP o che
comunque potrebbero esserlo nell’implementare una applicazione adatta agli
scopi delineati inizialmente nel presente elaborato.
Un singolo MPEG-2 TS (Transport Stream) può contenere più Packetized
Elementary Stream o PES (uno o più PES compongono un singolo programma o
servizio televisivo); per come sono strutturati i pacchetti del TS, un
demultiplexer può selezionare un Elementary Stream accettando tutti i pacchetti
con un dato PID (Packet IDentifier) e può quindi ricostruire un programma
completo conoscendo i PID dei relativi flussi audio, video ed eventualmente
flussi contenenti dati aggiuntivi.
La PSI (Program Specific Information) è la struttura dati attraverso la quale un
decoder riesce ad associare i vari PID, e quindi i singoli PES, ad un programma
televisivo. Essa si compone di quattro tabelle:
- la PAT (Program Association Table) stabilisce in un TS la corrispondenza tra
un programma e la definizione (ovvero la PMT) del programma stesso. Ogni TS
deve quindi contenere una PAT non criptata;
- la PMT ( Program Map Table) stabilisce la corrispondenza tra un programma
ed i PES che lo compongono (tramite i PID);
9
- la NIT ( Network Information Table) contiene i dati di rete relativi ai TS:
frequenze di canale, numeri dei transponder, modulazione, etc.;
- la CAT (Conditional Access Table) stabilisce la correlazione tra i sistemi di
oscuramento dei programmi ed i vari TS del flusso MPEG-2;
I dati di queste tabelle sono trasmessi in pacchetti di TS aventi uno specifico
PID riservato a tale scopo dalla specifica e la ritrasmissione ciclica di questi
particolari pacchetti avviene con una frequenza più elevata rispetto ad altri in
modo che il dispositivo terminale possa rapidamente accedere al programma
selezionato dall’utente.
La PSI fornisce quindi l’informazione necessaria al terminale per il
demultiplexing e la decrittazione di qualunque evento o servizio contenuto in un
TS. A tale struttura dati deve essere aggiunta una ulteriore serie di tabelle che
contengono informazioni sui servizi disponibili, sugli eventi, descrizioni testuali,
informazione per il raggruppamento dei servizi in categorie ed altro.
Tale insieme di tabelle prende il nome di DVB-SI (DVB Services Information) e
si compone dei seguenti elementi:
- BAT (Bouquet Association Table): contiene il nome del bouquet con i servizi
che lo compongono;
- SDT (Service Description Table): contiene le descrizioni dei servizi del
bouquet;
- EIT (Event Information Table): contiene i “metadati” relativi ai programmi, ad
esempio ora di inizio, durata, nome, etc.;
- RST (Running Status Table): contiene valori “flag” che descrivono lo stato di
un evento; gli stati possibili sono iniziato / non-iniziato;
- TDT (Time and Date Table): fornisce ora e data relativamente al fuso orario di
riferimento;
- TOT (Time Offset Table): fornisce ora e data della zona dove viene ricevuto il
TS;
- ST (Stuffing Table) : valida/invalida sezioni esistenti;
10
In un TS MPEG-2 è inoltre possibile incapsulare anche dati, genericamente
intesi, ovvero che non siano dati relativi a flussi audio o video né
necessariamente dei metadati che si applicano a tali tipi. Il metodo adottato nella
specifica DVB-T a questo scopo è il DSM-CC (Digital Storage Media –
Command and Control; standard ISO/IEC 12818-6); in particolare
l’implementazione di questo sistema permette di avere nello stream un
“broadcast filesystem”, una struttura dati assimilabile appunto ad un tradizionale
file system ed interpretabile in tal modo dal software di sistema del terminale,
così come da una applicazione MHP.
Dal punto di vista della trasmissione dati, la particolarità di una rete broadcast è
evidentemente quella di essere monodirezionale; infatti è il server (in questo
caso l’emittente televisiva) che decide quali dati, a cui possono accedere tutti i
client (in questo caso i terminali mhp), inviare. Nell’ambito di riferimento, tutti i
file di un filesystem appunto in broadcast vengono trasmessi ciclicamente ed il
terminale dovrà attendere quelli di cui necessita in base allo status in cui si
trova; la logica è del tutto analoga ad esempio a quella del servizio teletext nella
tv analogica. Questo meccanismo prende il nome di carosello o carousel (in
inglese) per analogia con il famoso programma televisivo degli anni 60 in cui un
certo numero di contenuti veniva riproposto ciclicamente.
Nel DSM-CC i dati, per essere trasmessi, vengono organizzati in moduli ai quali
viene aggiunta una descrizione che ne indica il contenuto. I moduli vengono poi
trasmessi ripetendo ciclicamente la stessa sequenza.
Questo standard, il DSM-CC, prevede due tipi di carosello:
- il Data Carousel: il “server” che trasmette i pacchetti dati non dà alcuna
indicazione sul contenuto dei medesimi; quindi in questo caso il terminale deve
analizzare tutti i pacchetti che riceve e ricostruire il contenuto per recuperare le
informazioni di interesse;
- l’Object Carousel: modello sicuramente più adatto alle situazioni più
complesse, si pone come layer aggiuntivo al Data Carousel e fornisce
funzionalità analoghe a quelle (basilari) di un tradizionale filesystem; in effeti è
questo il tipo di carousel adottato nello standard DVB-T;
Il contenuto di un Object Carousel è inteso essere una struttura di directory ad
albero; per la trasmissione essa viene divisa in più moduli, ciascuno dei quali,
come si era già accennato, include una ulteriore struttura dati che descrive il
contenuto; un modulo può contenere più file e/o directory, la dimensione
massima di un modulo è di 64 kB con l’eccezione di moduli dedicati ad ospitare
esclusivamente un singolo file di dimensioni maggiori di 64kB, qualora ve ne
fossero (non è ammesso invece dividere file che superino i 64 kB in più moduli).
Ovviamente i moduli vengono poi trasmessi ciclicamente, secondo appunto il
paradigma “a carosello”.
11
Il terminale, per ottenere uno specifico file, deve quindi attendere di ricevere il
modulo che lo contiene (potrà identificare il modulo dai metadati associati) per
poi analizzarlo ed estrarre il file di interesse.
Questo metodo può essere svantaggioso quando la quantità di dati è elevata ma
non è inconsueto che i terminali MHP abbiano capacità di memorizzazione
locale persistente, così che si possa effettuare anche una sorta di “caching”
locale in modo da non dover sempre attendere una nuova iterazione del carousel
per accedere agli stessi dati.
Il meccanismo descritto viene usato per trasmettere i file che compongono le
applicazioni MHP (quindi quantomeno uno o più file di bytecode Java) ed
eventuali altri file usati come “risorsa” dall’applicazione stessa.
Infine, per la natura intrinseca del sistema e del contesto, un “broadcast
filesystem” è ovviamente accessibile in sola lettura.
Un semplice esempio di un possibile contenuto di un Object Carousel:
a.jpg
b.jpg
c.jpg
d
d/t.txt
classes
classes/c1.class
classes/c2.class
classes/c3.class
[ 1256 bytes]
[ 4040 bytes]
[120346 bytes]
[
directory]
[ 26430 bytes]
[
directory]
[ 23020 bytes]
[ 59982 bytes]
[ 26947 bytes]
sì può configurare un Object Carousel con un “modulo1” che contenga a.jpg,
b.jpg, la directory d ed il file d/t.txt; il file c.jpg necessiterà di un
“modulo2” dedicato così come classes/c2.class può andare da solo in un
“modulo3”; infine la directory classes ed i restanti due file in un “modulo4”.
2.3 - Il Middleware MHP
L’Uso di un middleware aperto e standardizzato per la tv interattiva implica che
i produttori di dispositivi possano puntare a mercati multipli piuttosto che
sviluppare prodotti sulle specifiche di una particolare emittente e/o fornitore di
servizi; allo stesso tempo applicazioni progettate per essere eseguite nel
medesimo “ambiente” standard possono essere trasmesse in broadcast da diversi
fornitori di servizi; il tutto apre la possibilità di un mercato effettivamente
“orizzontale”.
12
Nei primi anni in cui erano disponibili servizi di TV digitale, le uniche risposte
alla necessità di API per eseguire applicazioni interattive sui STB erano sistemi
proprietari, e ciò certo non favoriva lo sviluppo di un mercato orizzontale. Il
DVB Project considerò una naturale evoluzione dei suoi successi nell’ambito
degli standard per quel che riguarda puramente la trasmissione l’iniziare a
lavorare, nel 1997, su un insieme di specifiche per un ambiente software aperto
e standardizzato il cui nucleo fosse basato su tecnologia Java. La prima
dimostrazione, comprensiva di trasmissione aerea, di applicazioni MHP e
ricevitori implementanti tali specifiche ebbe luogo all’esposizione IFA nel 1999
in Germania; la prima versione della specifica MHP venne ratificata dall’ ETSI
nel Luglio del 2000; i primi servizi facenti uso dello standard DVB-T ed MHP
vennero resi disponibili in Finlandia nel 2002.
Ad ora, sono tre le principali versioni (o “rami”, tenendo presente che per
ognuna esistono delle revisioni minori e che di nessuna delle tre è stato già
deprecato l’uso in quanto obsoleta, anzi in realtà solo implementazioni della
prima sono effettivamente diffuse) pubblicate della specifica MHP. Ogni nuova
versione è pensata principalmente per introdurre nuove caratteristiche, con un
occhio di riguardo al concetto, relativamente alla connettività Internet, che si
vada sempre più verso un “mondo a banda larga”. In ogni versione rimane
comunque pienamente supportata una modalità di utilizzo “broadcast only” e
una che non richieda elevate velocità all’interfaccia che il terminale implementa
(ed alla rete a cui essa si connette) per fornire le già citate funzionalità di “return
channel”.
Segue un elenco sintetico delle principali caratteristiche di ogni versione della
specifica.
- 1.0.x: “broadcasted applications”, dati via tcp/ip, implementabile su dispositivi
per ricezione DVB-S/C/T.
- 1.1.x: aggiunge alla 1.0.x “stored applications”, download di applicazioni via
tcp/ip, supporto a smartcard ed a video e grafica in alta definizione, video ondemand, e, opzionale, “browser” per l’appositamente definito linguaggio di
formattazione contenuti DVB-HTML.
- 1.2.x: aggiunge alla 1.1.x supporto alla IP-TV, secondo una modalità definita
internamente e denominata DVB-IPTV ed anche secondo paradigmi di terze
parti.
La più remota revisione del ramo 1.0, la 1.0.2 tuttora pubblicamente
disponibile, è datata Giugno 2001, mentre la più recente revisione della versione
1.2 risale ad Aprile 2007.
13
Sono anche state definite due estensioni, applicabili a ciascuna delle differenti
versioni della specifica: la prima, del 2004, atta a fornire ad un STB funzionalità
di PVR (Personal Video Recorder); la seconda, risalente a Febbraio 2008,
riguarda l’ambito dell’ accesso condizionale ed ha lo scopo di facilitare servizi
di PayTv complessi.
Semplificando al massimo, da un punto di vista funzionale MHP definisce cosa
il software presente sul teminale (compresivo appunto della parte che
implementi il middlware MHP) deve fornire in quanto ad ambiente di
esecuzione per applicazioni interattive (cosa che implica di conseguenza alcune
necessità a livello hardware, ma al produttore del STB viene lasciata massima
libertà nelle scelte specifiche, così come per il sistema operativo ed il come
implementare la specifica per quel che riguarda il middleware). MHP definisce
anche la forma in cui le applicazioni vengono distribuite (trasmesse) ai
terminali, inclusa la modalità di segnalare che applicazioni interattive sono
presenti in un transport stream.
MHP non è una specifica che si pone in concorrenza con altre ugualmente utili
alla distribuzione di contenuti ed all’interattività, come ad esempio possono
essere quelle di un linguaggio dichiarativo come HTML (specifiche che vanno
poi implementate in opportuni “motori” o “engine”, software): la linea di
principio alla base della specifica MHP suppone (sembrerebbe un po’
semplicisticamente per la verità) che per esempio un engine HTML possa senza
sostanziali problemi essere implementato come una applicazione MHP, e che
nel caso emergano nuovi requisiti da soddisfare sia più semplice e conveniente
aggiornare e ridistribuire una applicazione MHP che una “nativa” dipendente da
uno specifico hardware e/o sistema operativo.
MHP prevede quindi un sistema di distribuzione delle applicazioni che offre agli
operatori coinvolti nell’ ”anello” del flusso informativo relativo alla DTT le
potenzialità e la robustezza del modello “applicazioni scaricabili” (ricevibili
tramite trasmissione in broadcast, in questo caso), mantenendo per l’utente una
esperienza di fruizione agevole, priva di necessità di interventi di
gestione/configurazione da parte di quest’ultimo.
Dal punto di vista dell’architettura, usando la metafora degli strati, comune
relativamente ai sistemi software, il software che implementa l’ambiente di
esecuzione MHP, essendo infatti classificato come middleware, è uno strato che
opera sopra il sistema operativo specifico di ogni terminale e fornisce servizi ad
altri componenti software in esecuzione, a cui ci riferisce comunemente appunto
come “applicazioni MHP”. La specifica MHP prevede inoltre un’ulteriore
componente software, detto Application Manager, responsabile della attivazione
delle applicazioni ricevute nel transport stream e della gestione del ciclo di vita
delle stesse. L’ Application Manager, posto che le sue funzionalità e modalità di
intefacciamento con esso sono standardizzate, è previsto sia implementato come
sw “nativo” e non come applicazione MHP esso stesso.
14
Dando ora qualche riferimento sintetico ma un minimo concreto, MHP prevede
un ambiente di esecuzione il cui nucleo sia costituito da una Java Virtual
Machine e da un sottoinsieme delle classi disponibili in un ambiente Java
standard versione 1.1.8 così come definito dalle relative specifiche di Sun
Microsystems.
Per la precisione, il nucleo del middleware mhp è un sottoinsieme di Pesonal
Java 1.2, ambiente Java a suo tempo definito da Sun per l’implementazione nei
cosiddetti personal mobile devices (ed ora specifica/prodotto non più supportati,
sostituito da altre versioni di ambiente Java con scopi analoghi), ambiente
comuque a sua volta basato su quello standard 1.1.8.
Sono anche state incluse, in numero ridotto rispetto al resto, alcune API non
presenti nell’ambiente Java standard 1.1.8 ma in versioni successive (non
superiori alla 1.2.2 comunque); ad esempio le classi appertenenti al package
“java.security.cert” (utili alla gestione di certificati di sicurezza), così come
definite nelle specifiche Pesonal Java 1.2a; tale package non è incluso
nell’ambiente java standard 1.1.8, ma è presente a partire dalla versione 1.2.
Alcune delle API nell’insieme originario costituito dall’ambiente Java standard,
sono state escluse dalla specifica MHP, o perché ritenute non imprescindibili nel
contesto di destinazione e quindi sacrificabili per ridurre i costi in termini di
dimensione del middleware, o perché semplicemente ritenute del tutto non
necessarie nell’ambito specifico.
Inoltre, alcune delle classi dell’ambiente Java standard sono state modificate
nelle direttive per la loro implementazione; le ragioni di queste modifiche sono
di due tipi: ottimizzare il consumo di memoria e necessità specifiche di
funzionalità troppo differenti da quelle definite in Java standard; da notare che il
secondo caso può portare ad una differente “semantica” tra una data API in
MHP e la sua omonima in Java standard, pur con una forma identica.
Ad un nucleo così definito, sono stati aggiunti diversi gruppi di API, dovuti a
Sun e ad altre organizzazioni terze sia a quet’ultima che al DVB project, atti a
rispondere alle necessita specifiche di un contesto come quello di un ambiente di
esecuzione di applicazioni interattive per la TV digitale:
- Java TV 1.0: API precedentemente definite da Sun dirette appunto all’ambito
della tv digitale, sebbene prive di funzionalità specifiche per l’interazione con il
modello di organizzazione dell’informazione proprio dello standard trasmissivo
DVB; tra l’altro esse includono la classe “XLet” che presenta forti analogie con
il concetto di applet eseguita in un web browser e che definisce il modello di
riferimento per una applicazione MHP;
15
- JMF (Java Media Framework) 1.0: una parte di questa API è stata inclusa allo
scopo di fornire funzionalità di controllo dei flussi audio/video (ad esempio,
mettere in pausa, avviare la riproduzione etc. ); alcuni degli elementi adottati
sono stati anche modificati nella loro definizione per introdurre funzionalità più
specifiche per l’ambito televisivo (ad esempio, gestione sottotitoli, cambiamento
fattore di scala/posizionamento del frame, nuovo metodo di riferimento ai
contenuti nel formato URL, in modo da poter referenziare servizi e specifici
stream nella struttura dati DVB-T);
- DAVIC (Digital Audio VIsual Council) API 1.4.1 part 9: risponde alle
necessità per quanto riguarda l’implementazione di alcuni concetti basilari dello
standard MPEG, la sintonizzazione, il filtraggio delle sezioni di un flusso
MPEG, gestione di alcune risorse specifiche di un terminale per la tv digitale,
lettura delle informazioni relative all’accesso condizionale ai contenuti;
- HAVi (Home Audio Video interoperability) API: fornisce integrazione tra
video e grafica, widgets per l’interfaccia grafica adatti anche allo schermo di un
televisore tradizionale, ed altro relativo a gestire le difficoltà poste da un
tradizionale televisore in ambito grafico; è in sostanza l’API di riferimento per
quel che riguarda la creazione di una interfaccia utente grafica in una
applicazione MHP;
- DVB MHP API: sotto questo nome vanno tutte quelle API, appositamente
definite per la specifica MHP, che forniscono funzionalità, non presenti negli
altri gruppi di API adottati, specificamente utili per un terminale televisivo o atte
ad accedere alle informazioni in un flusso DVB-T; come ad esempio fornire
accesso alle tabelle di informazione definite dalla specifica DVB-T, l’uso del
“return channel”, o, in ambito di interfaccia utente, layout di teso adatto ad un
televisore, caricamento di font ricevuti in broadcast, composizione con
trasparenze tra diversi piani grafici.
Il primo passo nell’implementare un applicazione MHP consiste, per
definizione, nell’ “implementare”(da intendere anche nell’accezione specifica
del linguaggio Java) la già citata “interfaccia”(nuovamente, termine da intendere
nell’accezione specifica del linguaggio Java) Xlet, dato che essa ne definisce il
modello basilare di riferimento. Di seguito sarà quindi spesso equivalente usare
il termine “Xlet” come sinonimo di “applicazione MHP”.
16
Affinché una applicazione MHP possa essere attivata, sono necessarie alcune
condizioni preliminari:
- il terminale deve essere a conoscenza della disponibilità di tale applicazione
nel flusso DVB-T che viene ricevuto;
- deve essere possibile verificare che il terminale risulti conforme al profilo
MHP (si tornerà in seguito sui profili di conformità alla specifica MHP) di cui la
data applicazione necessita;
- l’utente deve essere abilitato all’esecuzione di tale applicazione nella fascia
temporale di interesse;
- devono essere disponibili tutti i dati necessari all’esecuzione dell’applicazione
o localmente o nell’Object Carousel incapsulato nel MPEG TS (nettamente più
comune la seconda opzione).
La presenza di una applicazione è segnalata in una tabella supplementare,
definita dalla specifica MHP, all’interno della precedentemente menzionata
struttura dati DVB-SI inclusa in un segnale DVB-T: ogni servizio o canale
televisivo che trasporta applicazioni deve avere anche una AIT (Application
Information Table), all’interno della quale si trovano tutte le informazioni
relative alle applicazioni attivabili mentre si è sintonizzati sullo specifico
servizio/canale.
Le principali informazioni associate ad una applicazione sono un identificatore
univoco, composto da un “Organization ID” di 32 bit (univoco per ogni
organizzazione che produca applicazioni MHP) ed un “Application ID” di 16
bit, i file che l’applicazione necessiterà di referenziare ed i class file del
bytecode Java che la compongono, ed eventuali parametri di avvio.
Quando un terminale si sintonizza su un canale contenente applicazioni,
L’Application Manager analizza la lista delle applicazioni relative al canale di
provenienza per poi disattivarle; quindi vengono caricate le applicazioni
segnalate nella nuova AIT; una applicazione per rimanere attiva nel passaggio
da un canale all’altro, dovrebbe essere segnalata nella nuova AIT come
“external application”.
MHP prevede per una applicazione o Xlet che dir si voglia, un ciclo di vita
precisamente strutturato che passa attraverso i quattro stati:
- loaded
- paused
- active
- destroyed
17
Le possibili sequenze di transizione sono indicate nel seguente diagramma:
(immagine tratta da http://www.mhp-knowledgebase.org )
Nel caso una Xlet generi un errore che ne implichi l’interruzione, viene sempre
portata nello stato “destroyed”, transizione alla quale è associato il rilascio delle
risorse allocate a tale applicazione.
L’ Interfaccia Xlet definisce dei metodi relativi alle transizioni di stato la cui
implementazione nella specifica applicazione provvederà ad eseguire le
eventuali azioni necessarie in corrispondenza della transizione associata; inoltre
ad una Xlet , tramite il metodo initXlet, nel passaggio allo stato paused ,
viene fornito un riferimento al relativo oggetto di tipo XletContext, al quale,
tramite i metodi appositamente definiti dalla specifica MHP la Xlet può
richiedere un cambiamento di stato o notificarne uno avvenuto all’ Application
Manager, nel caso la transizione di stato sia stata invocata ad esempio da
un’altra Xlet.
Infatti un’applicazione può raccogliere informazioni sulle altre ed interagire
anche relativamente alle transizioni di stato; inoltre tutti gli elementi nella AIT
al momento valida sono accessibili a tutte le applicazioni che li richiedano.
MHP non è uno standard monolitico che richiede la totale implementazione
della specifica al fine della conformità: alcune caratteristiche rimangono in
generale opzionali, inoltre, la specifica definisce tre differenti profili di
conformità, sostanzialmente in relazione di inclusione tra loro, riguardo alle
caratteristiche la cui implementazione è obbligatoria per ciascun profilo (ovvero:
il primo profilo si configura come un sottoinsieme delle caratteristiche richieste
nel secondo, idem tra il secondo ed il terzo).
18
Si hanno, citando gli elementi probabilmente più rilevanti:
- Enhanced Broadcast Profile: non impone l’implemnetazione della caratteristica
che va sotto il nome di “return channel” (sostanzialmente disponibilità di
funzionalità di connessione remota), include quanto serve per l’interattività
locale tramite le applicazioni trasmesse in broadcast; impone anche qualche
restrizione sui parametri delle immagini JPEG che possono essere usate;
- Interactive Broadcast Profile: aggiunge l’imposizione di una qualche forma di
return channel (l’hardware per la connessione ed il protocollo a livello fisico
vengono lasciati alla scelta dell’implementatore) e della disponibilità su questa
connessione dei protocolli tcp/ip e dns; http 1.0/1.1 sono opzionali, sebbene è
molto comune che un teminale MHP fornisca quantomeno anche
l’implementazione del protocollo http 1.0;
- Internet Access Profile: profilo definito solo a partire dalla versione 1.1 della
specifica, aggiunge supporto obbligatorio ad http 1.1 e prevede come opzionale
un client in grado di interpretare contenuti DVB-HTML, un linguaggio di
makup definito ad hoc a partire da varie specifiche del W3C.
L’obbligatorietà o meno di alcune caratteristiche affinché un terminale sia
conforme ad un dato profilo, possono variare a seconda che ci si riferisca alla
versione 1.0.x o ad una successiva della specifica MHP.
La seguente tabella riporta la situazione di varie caratteristiche relativamente a
ciascun profilo, in base alla specifica 1.0.x ed 1.1.x:
19
(tabella tratta da http://www.mhp-interactive.org)
20
La AIT contiene per ogni Xlet una struttura dati denominata Application
Descriptor che contiene l’informazione riguardo a di quale profilo (e quale
versione) l’applicazione necessita per poter essere eseguita; quando il terminale
riceve una nuova AIT, come parte del processo di analaisi di questa, controllerà
gli identificativi di profilo ed i numeri di versione associati ad ogni applicazione;
ciò permette di determinare a priori quali applicazioni sono eseguibili e di non
tentare eventualmente l’esecuzione di applicazioni che necessitino di risorse
non implementate.
Inoltre, una applicazione in esecuzione può determinare a quale profilo e
versione è conforme il terminale, così come quali caratteristiche opzionali sono
eventualmente supportate.; questo avviene attraverso apposite classi dell’API
MHP che “riflettono”, tramite i dati/oggetti contenuti, alcune proprietà dello
specifico sistema (In modo analogo per esempio alla classe System in un
ambiente java standard); ciò può tornare utile nel caso una applicazione voglia
verificare, un volta attiva, se può eventualmente “fare qualcosa” che vada oltre
le possibilità garantite dal profilo a cui è stata associata in sede di generazione
della AIT.
21
3 - MHP, Alcune API e Funzionalità di Specifico Interesse
Si prenderanno in esame qui di seguito alcune caratteristiche funzionali definite
dalla specifica ed alcune interfacce di programmazione tra quelle che
compongono l’API MHP, particolarmente, per certo o potenzialmente, utili
nell’ottica di determinare quanto necessario, per quel che riguarda il fronte di
sviluppo di applicazioni MHP, in relazione agli obiettivi inizialmente indicati;
ovvero, principalmente:
- acquisire dati, o accedere ai medesimi altrimenti acquisiti, nel terminale MHP
da un dispositivo esterno locale tramite connessione conforme allo standard EIA
(Electronic Industries Alliance) RS-232 con connettore DE-9 (la comune “porta
seriale”);
- inviare tali dati ad un sistema remoto, usando le API per la gestione del return
channel e quelle per le funzionalità di rete comuni ad un ambiente di esecuzione
Java standard.
3.1 - org.dvb.test.DVBTest
Si prende in esame questa classe semplicemente in quanto la definizione della
medesima è l’unica parte della specifica MHP in cui si menziona la connessione
RS-232.
La classe DVBTest è pensata per permettere ad applicazioni di test di
registrare, su un sistema esterno al terminale MHP, messaggi di debug durante
l’esecuzione o messaggi relativi allo stato di terminazione.
Relativamente alla definizione delle azioni corrispondenti ai metodi (metodi
pubblici si sottintende sempre) definiti in questa classe che implementino tali
funzionalità, si lascia a alla particolare implementazione del middleware MHP la
scelta di quale connessione fisica si debba utilizzare per le citate operazioni di
logging. Una tra le varie opzioni suggerite, è appunto la connessione RS-232.
L’unico di questi metodi (i quali tutti tra l’altro restituiscono il tipo void) la cui
“semantica” sia connessa ad un flusso dati in ingresso nel terminale MHP
tramite porta seriale (se tale dovesse essere la scelta della connessione in una
qualche specifica implementazione) è
prompt(java.lang.String id, int controlCode,
java.lang.String message)
22
questo metodo è inteso essere usato per sincronizzare un client MHP di test con
un test-server; il metodo deve essere bloccante finché il client non riceve da
parte del server un “acknowledgment” negativo o positivo per lo specifico
messaggio inviato. E nelle linee guida per l’implementazione, tra le opzioni
suggerite per la connessione attraverso cui rivevere l’ acknowledgment, figura
RS-232.
3.2 - java.lang.Runtime
Ogni applicazione può disporre di un oggetto della classe
java.lang.Runtime che permette di interagire con l’ambiente in cui
l’applicazione stessa è in esecuzione. Una istanza di Runtime si ottiene
attraverso l’apposito metodo statico (non è contemplato che una xlet crei una sua
nuova istanza di questa classe, quindi la definizione di Runtime non prevede
costruttori):
public static Runtime getRuntime()
Questa classe oltre a permettere di ottenere informazioni sull’ambiente di
esecuzione Java/MHP e sulle risorse di sistema, permette anche di caricare ed
eseguire codice binario “nativo” per lo specifico s.o. su cui è in esecuzione il
middleware java/MHP, con i metodi
public void load(String filename)
public void loadLibrary(String libname)
ed i metodi “exec” distinti tramite differente definizione degli argomenti. Questi
ultimi in particolare permettono non solo di avviare un file eseguibile nativo in
un processo separato, ma anche di eseguire comandi implementati dallo
specifico sistema operativo su cui è in esecuzione l’ambiente java/MHP:
public Process exec(String command) throws IOException
esegue in un processo separato il comando di sistema (o file eseguibile,
referenziato tramite path completo) rappresentato dalla stringa command;
restituisce un oggetto di tipo Process, rappresentativo del nuovo processo
generato e che permette la gestione del medesimo;
public Process exec(String command, String[] envp)
23
throws IOException
esegue in un processo separato il comando di sistema (o file eseguibile,
referenziato tramite path completo) rappresentato dalla stringa command,
settando le variabili di ambiente rappresentate dalle stringhe nell’array envp
(secondo la sintassi, per ogni stringa, “nome=valore”); se envp è null, il
nuovo processo eredita la configurazione delle variabili di ambiente del processo
padre; restituisce un oggetto di tipo Process, rappresentativo del nuovo
processo generato e che permette la gestione del medesimo;
public Process exec(String[] cmdarray) throws IOException
public Process exec(String[] cmdarray, String[] envp)
throws IOException
metodi con semantica identica ai due precedentemente descritti, eccetto per il
fatto che il comando da eseguire comprensivo di eventuali argomenti viene
passato come array di stringhe anziché come stringa unica.
Nell’ottica degli obiettivi inerenti il presente elaborato, la classe Runtime può
risultare di particolare interesse nella misura in cui i metodi appena descritti
permettano di eseguire comandi propri del sistema operativo del STB MHP
che avviino un processo di acquisizione dati dalla connessione RS-232,
eventualmente memorizzando poi tali dati in una memoria non volatile del STB,
nel caso non vi sia una modalità più diretta per renderli disponibili alla
applicazione MHP.
La classe Runtime è definita nelle specifiche per un ambiente Java standard ed
adottata nella specifica MHP senza particolari direttive esplicite riguardo alla
sua implementazione, ad eccezione del fatto che è contemplata la possibilità che
i metodi descritti nel presente paragrafo inoltrino una eccezione di tipo
SecurityException a seconda delle scelte implementative relative alle
politiche di sicurezza scelte per lo specifico STB.
3.3 - Broadcast Filesystem
Nell’ambiente di esecuzione MHP, spesso l’unico file system a cui una
applicazione accede è quello di tipo comunemente detto “broadcast”, riferendosi
alla struttura dati implementata e trasmessa nel TS secondo il paradigma del
DSM-CC Object Carousel, ed a cui il middleware MHP fornisce accesso
fornendone alle applicazioni, tramite le apposite API, una astrazione come
filesystem gerarchico; filesystem al quale, per ovvie ragioni inerenti
l’unidirezionalità di un flusso dati in broadcast, lato applicazioni MHP, si ha
accesso in sola lettura.
24
E’ previsto di poter utilizzare alcune delle classi del package Java standard
java.io al fine di compiere determinate operazioni relative ad un file
presente in un Object Carousel, tramite l’opportuno oggetto che lo rappresenti in
una applicazione; tuttavia la natura particolare del file system in broadcast ha un
certo impatto sul modo in cui l’implementazione delle classi di java.io
funzionano in tali circostanze.
La differenza più ovvia, oltre a quella di avere un file system read-only,
riguarda la latenza: avendo a che fare con un DSM-CC Object Carousel, il STB
deve attendere che il modulo contenente la risorsa, a cui l’applicazione ha
richiesto accesso, venga ricevuto prima che il file possa essere effettivamente
disponibile alla xlet, e ciò può implicare anche tempi di attesa dell’ordine di
alcuni secondi.
Dal punto di vista dello sviluppo di una applicazione MHP e del prepararne la
trasmissione, il problema, specie se seguente l’interazione dell’utente con la
xlet, della possibile latenza elevata può essere mitigato sostanzialmente in due
modi:
- configurare l’ object carousel in modo che i file richiesti più spesso dalla
applicazione siano trasmessi sufficientemente di frequente tanto da ridurre la
latenza a livelli che si ritengono accettabili; è opportuno inoltre inserire tali file
se possibile nel medesimo modulo del carousel, in quanto questo facilita il
caching locale, se operato dal STB;
- scrivere il codice dell’applicazione in modo che il file nell’ object carousel a
cui si vuole accedere sia richiesto, quando la specifica esigenza è
sufficientemente predicibile, con un opportuno anticipo sul momento in cui esso
sarà effettivamente necessario tanto da mantenere i tempi di attesa accettabili.
Le API relative al DSM-CC Object Carousel definite in MHP aggiungono a
quelle Java standard per gestione ed accesso ai file il supporto a caratteristiche
proprie di un filesystem basato su un object carousel, estranee ad un filesystem
convenzionale, come il caricamento asincrono (ad esempio poter leggere il
contenuto di un file senza che l’operazione di input sia bloccante fino alla sua
terminazione). Tali API forniscono inoltre accesso a quelle parti di un carousel
non relative alla trasmissione di file/directory: infatti, come anche dal nome
stesso, un DSM-CC Object Carousel è definito in modo tale da trasmettere, in
generale, “oggetti”, non solo file, e quindi poter incorporare altri tipi di data
stream oltre a quelli utili a rappresentare un filesystem.
Una seconda rilevante differenza tra un filesystem come vista a livello
applicazione di un DSM-CC Object Carousel, rispetto ad un file system
convenzionale, riguarda il l’importanza del caching.
25
Molti STB mantengono una cache di alcuni moduli del carousel, allo scopo di
ridurre la latenza in fase di accesso da parte delle applicazioni; il broadcaster
può però aggiornare il contenuto dell’object carousel mentre esso continua ad
essere trasmesso, quindi si può verificare l’eventualità che i moduli nella cache
diventino obsoleti: per gestire questio tipo di situazione, le API relative al
broadcast file system permettono anche che l’applicazione scelga se i file che
richiede vengano caricati da:
- la cache se il file richiesto è presente, altrimenti dall’accesso effettivo
all’object carousel (è questa la politica definita come default per un ambiente
MHP)
- la cache, esclusivamente; si assicura così la minima latenza ma si massimizza
il rischio di accedere ad una risorsa non aggiornata.
- l’object carousel, esclusivamente; si garantisce che il file cui si accede sia la
copia più aggiornata disponibile, ma si ha la massima latenza per quel che
riguarda l’operazione di accesso alla risorsa.
Le API definite nella specifica MHP per accedere alle informazioni contenute
nel DSM-CC Object Carousel sono costituite dalle classi del package
org.dvb.dsmcc.
Quelle il cui uso è di solito più comune e rilevante sono DSMCCObject e
ServiceDomain.
La prima estende la classe Java standard java.io.File e come prevedibile
gli oggetti di tipo DSMCCObject rappresentano nodi del filesystem in
broadcast, siano essi file o directory, analogamente a quanto implementato dalla
classe estesa, File, per file system convenzionali. Oggetti di tipo
DSMCCObject possono essere usati in maniera analoga a quelli di tipo File,
con l’eccezione ovvia che cercare di effettuare operazioni di scrittura nell’object
carousel causerà condizioni di errore e con l’ulteriore restrizione, imposta dalla
specifica MHP, che per leggere da oggetti di tipo DSMCCObject
ci si limiti all’uso delle classi java.io.FileInputStream o
java.io.RandomAccessFile.
La classe ServiceDomain deve fornire la definizione per oggetti
rappresentativi dell’astrazione che il middleware fornisce di un object carousel
nel suo insieme, fornendo accesso ad informazioni che vanno oltre quanto
relativo un singolo file o directory; ad esempio fornisce il metodo
getMountPoint() che restituisce un oggetto DSMCCObject rappresentante
il path simbolico in cui viene montato il filesystem “virtuale” che permette di
accedere ai contenuti di un carousel.
26
3.4 - Memorizzazione Persistente
Tipicamente, l’unico filesystem a cui una applicazione MHP accede, è quello
trasmesso in broadcast secondo il paradigma del DSM-CC carousel.
E’ tuttavia possibile che terminali anche MHP 1.0.x offrano, sebbene non
imposta da specifica, la possibilità di memorizzazione locale persistente su un
qualche tipo di risorsa interna; nella gran parte dei casi si tratta di una alquanto
limitata quantità di un qualche tipo di NVRAM, (“flash memory” con ogni
probabilità). Non è inoltre possibile, per direttiva da specifica, utilizzare tale
risorsa per memorizzare le applicazione stesse (relativamente ad MHP 1.0.x)
Il package org.dvb.io include alcune classi che permettono l’accesso a
questo tipo di storage.
La directory radice del file system persistente (della parte disponibile per le
applicazioni, si intende) si ottieene dalla proprietà di sistema identificata dalla
stringa “dvb.persistent.root”. Vi sono alcune limitazioni di accesso per
le
applicazioni:
la
directory
<persistent_root>/<organization_id>/
<application_id> deve essere e leggibile e scrivibile da parte della relativa
applicazione, così come qualunque sottodirectory; le parti al di fuori di tale
ramo, quello che ha path base corrispondente a <persistent_root>, della
gerarchia ad albero non dovrebbe essere accessibile né in lettura né in scrittura
per le applicazioni in genere; la directory <persistent_root>/<organization_id>/
<application_id> è considerabile una sorta di “home directory” per
l’applicazione, ogni Xlet ne ha una, tali directory vengono automaticamente
create dal sistema.
Quale che sia il filesystem utilizzato sulla risorsa di storage persistente, la
specifica MHP impone un insieme standard di attributi che devono essere
supportati per un file; ad essi si può accedere tramite la classe
org.dvb.io.FileAttributes. Uno di questi attributi è owner; per la
directory <persistent_root>/<organization_id> il proprietario è sempre
identificato con il terminale MHP o “sistema” che dir si voglia (in partica, nel
contesto, assimilabile al superuser/admin di altri ambiti); l’applicazione sarà
invece proprietaria della sua “home” e di tutto il relativo contenuto.
Il modello dei permessi segue in gran parte quello unix tradizionale, con
permessi di accesso distinti per tre entità definte “owner”, “organizzation” e
“world”, ovvero rispettivamente l’applicazione proprietaria del file/directory, le
applicazioni con il medesimo organizzation id di quella owner, e tutte le altre
applicazioni. I permessi possibili riguardano l’accesso in lettura e/o scrittura e
comunemente ci si riferisce ad essi nei termini rispettivamente di “read” e
“write” permission; sono rappresentati, per quel che riguarda la gestione da
parte
di
una
xlet,
da
un
oggetto
di
tipo
27
org.dvb.io.FileAccessPermissions. I permessi possono essere
settati con il metodo setPermissions di questa classe, metodo che prende
come argomento sei valori boolean che a coppie si riferiscono ai permessi
read e write rispettivamente per world, organizzation ed owner; per rendere
effettive le impostazioni sul file/directory di interesse, si dovrà poi associare ad
esso l’oggetto di tipo FileAccessPermissions, tramite quanto messo a
disposizione dalla classe FileAttributes, procedura che verrà
dettagliatamente esaminata in seguito.
La specifica MHP definisce anche gli attributi “expiration date” e “priority” per
un file nell’area di memorizzazione persistente dedicata alle applicazioni,
attributi gestibili tramite gli appositi metodi della classe FileAttributes,
(ovviamente in base a quali diritti di accesso l’applicazione possiede sul file di
interesse).
Il primo attributo rappresenta la data oltre la quale il file è considerato non più
utile dal relativo owner, e diventa quindi un candidato alla rimozione nel
moemento in cui il sistema deve liberare spazio sul dispositivo di storage. Una
applicazione può settare questo attributo sui file di cui è owner, ma
l’impostazione definitiva di tale data e la eventuale rimozione del file è definita
essere comunque prerogativa del sistema in caso di capacità di memorizzazione
in esaurimento; questo per evitare che una applicazione possa aggirare il
meccanismo di gestione dello spazio di storage settando una data futura
irragionevolmente lontana.
Il valore dell’attributo priority notifica al sistema l’importanza relativa di un
dato file, maggiore è la priorità impostata minore sarà la probabilità che il file
venga rimosso nel momento in cui il sistema avrà bisogno di aumentare la
capacità di memorizzazione disponibile, sia che il file sia “scaduto” o anche nel
caso non lo sia, se l’eliminazione dei soli file con expiration date trascorsa non
viene rilevato come sufficiente alle necessità del sistema, definite in genere in
base ad algoritmi che prevedono di mantenere un minimo di capacità di storage
sempre disponibile, almeno per le applicazioni al momento in esecuzione.
3.5 - Return Channel
La conformità ai profili “Interactive Broadcasting” e “Internet Access”
(quest’ultimo definito solo a partire dalla versione 1.1 della specifica MHP)
prevede che l’hardware ed il software del STB permettano la disponibilità per le
applicazioni, attraverso le apposite API, di una connessione di rete.
28
Dal punto di vista del tipo di dispositivo da usare per la connessione e relativo
protocollo di livello fisico viene lasciata totale libertà di scelta al produttore di
un STB, la scelta tuttora più comune nel mercato italiano, almeno su quei
prodotti destinati all’utenza di massa, è quella di connessione tramite modem
PSTN. La connessione dial-up è tipicamente usata da una applicazione per
effettuare il login ad un ISP e poter quindi poi sfruttare almeno in parte le
potenzialità di Internet.
A livello di protocolli, MHP 1.0.x impone che sia resa disponibile alle
applicazioni l’implementazione dello stack TCP/IP fino al livello del protocollo
TCP. L’implementazione di protocolli di livello applicazione come ad esempio
HTTP è opzionale o comunque lasciata alle applicazioni MHP stesse qualora ve
ne sia necessità. Da notare che lì implementazione di HTTP 1.0 è comunque
molto comune anche in STB rispondenti alle specifiche 1.0.x, sebbene
l’implementazione di tale protocollo congiuntamente alla variante HTTPS sia un
requisito di conformità solo a partirte dal profilo MHP 1.1 “Internet Access”.
La dotazione a livello di stack di rete, riguardo solamente a quanto imposto
dalle specifiche MHP 1.0.x, alle quali ci si attiene nel presente documento,
appare quindi piuttosto “scarna” rispetto ai tempi ma bisogna rilevare che è in
linea con lo scopo principale dello standard MHP, ovvero l’interoperabilità tra
sistemi dedicati alla ricezione della DTT e all’interattività su tale base, e non il
fornire una “rich client platform” per l’interconnessione tra sistemi diversi, non
primariamente almeno, sebbene la dotazione in relazione alle funzionalità di rete
venga progressivamente ampliandosi con il succedersi delle versioni della
specifica.
La differenza essenziale tra l’uso delle API per le funzionalità di rete in una
applicazione per ambiente Java standard ed in una applicazione MHP deriva dal
fatto che nella definizione dell’ API Java standard si assume che il sistema
disponga di una connessione di rete di tipo always-on o che eventualmente la
connessione sia già stata effettuata a monte dell’esecuzione dell’applicazione.
MHP include il package standard java.net che contiene le note classi per le
funzionalità di rete, ma definisce delle API aggiuntive che permettono ad una
xlet di gestire una sessione di connessione ad un sistema remoto, sulla base di
parametri quali numero telefonico e dati utente per il login, nel caso il return
channel sia implementato, come è nella maggioranza di casi, tramite
connessione di tipo dial-up; di solito tali funzionalità sono prevedibilmente
utilizzate per gestire una sessione con un ISP. Ovviamente tali api permettono
anche di verificare se il return channel disponibile sia di tipo always-on, come
ad esempio una connessione Ethernet.
29
Una ulteriore differenza rispetto ad un ambiente Java Standard, può essere la
non disponibilità di API relative a java.net che siano state definite da Sun
successivamente alla versione 1.1.8, la quale come visto precedentemente
costituisce il nucleo su cui sono basate le API definite dalla specifica MHP.
(Cosa tuttavia scontata ed applicabile a tutti gli ambiti coperti dalle API definite
per la piattaforma in esame).
Le API per la gestione delle sessioni di connessione sono costituite dalle classi
appartenenti al package org.dvb.net.rc; Vi sono tre classi di particolare
interesse in relazione alle funzionalità menzionate:
- RCInterface fornisce all’applicazione oggetti rappresentativi
dell’interfaccia di rete, attraverso alcune proprietà di base come il tipo di
connessione (PSTN, ADSL, Ethernet, ISDN, etc.) ed il data-rate nominale;
- ConnectionRCInterface, sottoclasse di RCInterface, fornisce
all’applicazione una rappresentazione di una interfaccia di rete non di tipo
always-on, per la quale bisogna gestire le operazioni di connessione e
disconnessione; come appunto nel caso molto comune per i STB più diffusi di
una connessione dial-up PSTN. Operazioni per le quali tale classe definisce
appositi metodi, così come per determinare a quale sistema remoto effettuare la
connessione, tramite il metodo
public void setTarget(ConnectionParameters target)
il cui argomento è un oggetto di tipo ConnectionParameters che modella
i parametri necessari ad effettuare la connessione remota;
- ConnectionParameters, come accennato appena sopra, è una classe una
cui istanza contiene le informazioni necessarie ad effettuare una connessione
dial-up ad un determinato un sistema remoto. I parametri rappresentati da tale
classe sono quelli tipici ad esempio dello scenario in cui si effettua il login ad un
ISP tramite modem PSTN e sono esposti in maniera decisamente
autoesplicativa nella definizione dei due costruttori disponibili per la classe
ConnectionParameters:
public ConnectionParameters(String number, String user name,
String password);
public ConnectionParameters(String number, String user name,
String password, InetAddress[] dns);
Per le API descritte nel presente paragrafo, così come per le altre trattate in
questo capitolo, si mostreranno in seguito esempi d’uso dettagliati, correlati agli
obiettivi generali dell’elaborato.
30
4 - Considerazioni Operative e Qualche “Proof of Concept”
4.1 - Acquisizione Dati da Interfaccia RS-232
E’ questo il tipo di connessione locale verso un altro dispositivo nettamente più
comune in un STB MHP, nel caso il prodotto in questione fornisca tale
possibilità.
Alcuni dispositivi di comune uso in ambito medico, quali ad esempio alcune
macchine per l’ ECG, anche con riferimento a normali modelli in uso e/o in
commercio nel settore, dispongono della medesima interfaccia, il che rende
questo tipo di interfaccia attualmente la principale candidata a mezzo di
interconnessione per acquisire dati in STB MHP e poi inviarli ad un sistema
remoto tramite opportuna applicazione, il tutto nell’ottica di poter sfruttare la
piattaforma DTT anche per servizi come appunto tele-monitoraggio di parametri
biometrici in ambito medico.
Per quanto riguarda la parte di acquisizioni dati da porta seriale e di renderli
quindi disponibili ad una Xlet, come ovvio, è innanzitutto necessario dotarsi di
un STB (o dispositivo equivalente) che implementi tale connessione.
Putroppo, non essendo l’intefaccia RS-232 imposta come imprescindibile dalla
specifica MHP, la sua presenza non è consueta nei comuni modelli in
commercio e distinati all’utente finali; non è tuttavia in assoluto problematico
reperire terminali MHP che siano dotati di porta seriale: infatti la presenza di
questa è praticamente un fatto scontato nei modelli prototipo di prodotti poi
destinati all’utente finale ed usati per i test di conformità e nei modelli dedicati
allo sviluppo di applicazioni MHP; in entrambe queste categorie la connessione
RS-232 è implementata principalmente allo scopo di fornire funzionalità di
logging in locale, esternamente al terminale MHP, in maniera conforme a
quanto definito nella classe org.dvb.DVBTest precedentemente menzionata.
Ovviamente sarà il secondo tipo di terminale, un STB per sviluppo applicazioni,
quello sui cui indirizzare opportunamente la scelta nell’ambito dello sviluppo di
una applicazione che implementi le caratteristiche discusse, anche perché con
ogni probabilità quello più semplice da reperire che non un prototipo.
Lo svantaggio della quasi certa non disponibilità della connessione seriale nei
comuni STB destinati all’utente finale, è un inevitabile aumento dei costi
dovendosi indirizzare su prodotti relativamente “di nicchia”. Tuttavia in alcuni
casi all’assenza di una porta seriale in un comune STB commerciale si può
ovviare anche accedendo alla scheda principale di questo, che in alcuni modelli
è comunque provvista di un controller per una connessione RS-232 e di
opportuni contatti elettrici cui collegare un opportuno connettore DE-9.
31
Per quanto riguarda le API MHP, facendo in realtà ipotesi piuttosto “forzate”,
l’unico riferimento possibile è il già illustrato metodo
prompt(java.lang.String id, int controlCode,
java.lang.String message)
definito in org.dvb.DVBTest.
In linea del tutto teorica, si potrebbe ipotizzare che esistano STB tali che
rendano fattibile acquisire dati trami seriale da un altro dispositivo come se
questi fossero l’acknowledgement di un test-server; ma bisognerebbe anche
ipotizzare che tale implementazione sia accompagnata da informazioni e
funzionalità aggiuntive e specifiche del dato prodotto tali da permettere in
qualche modo di “recuperare” i suddetti dati, visto che prompt non restituisce
nulla e la definizione della semantica del metodo nega la possibilità che venga
scritto qualcosa nei parametri passati ad esso.
Un’ulteriore ipotesi, ancor più ottimistica, potrebbe essere che vi siano STB in
cui la specifica ’implementazione del middleware MHP aggiunga, estendendo
DVBTest, metodi analoghi a quelli per il logging definiti in DVBTest che
utilizzino l’intefaccia RS-232 e che permettano (a differenza di log e
terminate in DVBTest così come definiti da specifica MHP) di utilizzare
tale connessione anche per un flusso dati in ingresso al STB.
E’ comunque documentato che in alcuni STB è effettivamente possibile
acquisire dati da connessione seriale, ma è più realistico e con probabilità di
riscontri pratici coerenti molto maggiore, supporre che una tale necessità possa
essere soddisfatta operando in maniera specifica in base alle funzionalità proprie
del particolare prodotto che si va ad utilizzare “sul campo”.
Ad esempio, nell’ 8° Congresso di Telemedicina ed Informatica Medica, è stato
riportato come siano stati acquisiti dati in output da un macchina ECG in un
STB MHP tramite connessione RS-232: è stato possibile superare la mancanza
di un paradigma standardizzato relativamente al middleware MHP tramite
comandi propri del Sistema operativo dello specifico STB utilizzato: tramite un
apposito comando di sistema esso memorizza i dati in ingresso alla porta seriale
all’interno di file residenti nella risorsa di memorizzazione persistente .
Uno scenario analogo a quest’ultimo descritto è probabilmente quello più
probabile in una situazione in cui si vogliano acquisire dati da connessione
seriale. Tale scenario presuppone per il STB l’ulteriore requisito di avere risorse
per la memorizzazione persistente, fatto comunque molto probabile in un STB di
sviluppo e non inconsueto in generale, sebbene la capacità di memorizzazione
nei STB destinati all’utenza di massa ed a basso costo sia piuttosto ridotta.
32
Con tali presupposti, rendere disponibili i dati acquisiti ad una applicazione
MHP, che poi provveda ad inviarli ad un sistema remoto sfruttando le API per le
funzionalità di rete, non risulta particolarmente arduo (specie se i comandi di
sistema propri del STB utilizzato permettono di decidere il path di
memorizzazione e/o di settare i permessi sui file), dato che come visto in
precedenza la specifica MHP formalizza modalità standard per accesso da parte
di una Xlet anche al filesystem persistente.
Per eseguire comandi del s.o. del STB, che a seconda dello specifico dispositivo
potrebbero permettere di acquisire dati da RS-232, da memorizzare poi
eventualmente nel filesystem persistente, si può considerare l’uso dei metodi
“exec” della classe java.lang.Runtime ereditata dalle api java standard;
la specifica MHP tuttavia non dà direttive esplicite sulla implementazione di tale
classe o su eventuali limitazioni nell’uso possibile da parte di una Xlet (per
ragioni inerenti la gestione della sicurezza, ad esempio); le possibilità effettive
restano quindi da valutare caso per caso, in base agli strumenti a disposizione.
Se quanto disponibile in java.lang.Runtime non dovesse essere utilizzabile
proficuamente in una specifica implementazione di MHP, si dovrà
probabilmente considerare una soluzione in cui i comandi di interesse propri del
s.o. del STB vengano impartiti tramite la connessione RS-232 stessa dal
medesimo dispositivo da cui acquisire dati o da uno stadio intermedio
implementato appositamente: una soluzione analoga cioè a quanto avviene nella
situazione in cui si utilizzano una connessione RS-232 tra un STB ed un PC ed
appositi software che, su tale canale hardware, permettono di accedere alla
memoria ed alla risorsa di storage non volatile del STB, allo scopo di caricare
applicazioni e leggere/scrivere file nel terminale MHP; infatti alcuni sistemi
operativi per STB accettano in input dalla connessione seriale dei comandi che
vengono poi eseguiti dal sistema stesso, come appunto leggere da file/directory
esistenti o crearne di nuovi, scrivendo in un nuovo file i byte ricevuti in seguito
al comando di scrittura.
Ovviamente in scenari come quelli precedentemente menzionati, relativi ad
esempio a trasmettere tramite STB dati relativi al monitoraggio di parametri
biometrici, ha poco senso considerare un PC che faccia da ponte tra un
dispositivo diagnostico ed il STB e con il quale, PC, si debba comunque
interagire, in quanto sarebbe allora inutile il STB stesso; se con un dato
terminale MHP non potrà essere la xlet ad impartire gli opportuni comandi al
sistema, sarà conveniente che tale compito venga assolto, come già menzionato,
dal dispositivo stesso da cui acquisire dati, se possibile, o da uno apposito che
funga da stadio intermedio, che sia a basso costo e che non richieda l’intervento
dell’utente finale.
33
Per quanto riguarda la codifica dei dati stessi internamente all’applicazione
MHP e relativamente alla loro trasmissione, classi dell’API Java standard ed
ereditate dall’ambiente MHP, quali varie appartenenti al package java.io e la
classe java.lang.String permettono un trattamento piuttosto agevole sia
che si voglia “maneggiare” l’informazione in maniera “neutra” a livello di array
di byte contenente in toto quanto acquisito (lasciando quindi eventuali compiti
di interpretazione/parsing al sistema remoto che riceverà i dati), sia che voglia
adottare una forma più strutturata; ad esempio è comune che un dispositivo di
uso comune in ambito medico come un ECG sull’output seriale invii un flusso
di byte che corrisponde ad un formato testuale secondo una delle codifiche di
carattere più diffuse (tipicamente ASCII), ed in un simle caso si potrebbe
ritenere opportuna all’interno della Xlet, a seconda degli specifici obbiettivi di
progetto, una gestione “consapevole” dei dati acquisiti rispetto al formato di
codifica (ad esempio, banalmente, se si volesse visualizzarne una parte anche
come output a video).
4.2 - Accesso al Filesystem Persistente
Stante quanto appena considerato nel precedente paragrafo, è possibile che
accedere ai file su una risorsa di memorizzazione persistente in un STB MHP
possa essere essenziale ai fini di avere disponibili in una Xlet dati acquisiti da
una connessione RS-232.
Si espongono qui di seguito alcuni passi operativi ed esempi di codice Java
(ovviamente idoneo all’esecuzione in un ambiente MHP) utili a soddisfare tale
eventuale necessità.
L’ambiente MHP permette alle applicazioni autorizzate di accedere ad uno
specifico sotto-albero del filesystem su un dispositivo di memorizzazione
persistente eventualmente presente nel STB.
Affinché ad una Xlet sia concesso l’accesso al filesytem persistente, essa deve
essere segnalata nella AIT con un Application ID che sia nel range 4000 – 7FFF
(valori esadecimali).
Inoltre l’accesso alle specifiche risorse, relativamente alla detta area di storage,
tra quelle possibili, deve essere richiesto tramite un "Permission Request File"
che deve essere un file di testo con contenuto conforme alla sintassi XML ed al
DTD specificato nel paragrafo 12.6.2 delle specifiche MHP 1.0.x; questo file
deve essere presente nel file system trasmesso tramite il modello dell’ object
carousel e deve essere nella stessa directory dove risiede il bytecode della classe
principale della Xlet (ovvero la classe usata per l’avvio dell’applicazione, quella
che implementa l’interfaccia Xlet stessa) ed il nome di tale file deve essere
34
conforme allo schema
dvb.<application name>.perm, dove
<application name> è il nome della detta classe principale così come è
scritto nel codice java (e nel nome del relativo file sorgente o .class quindi),
completo del prefisso che indica il package se tale classe è appunto definita
come parte di un package. Ad esempio:
dvb.TestXlet.perm
oppure
dvb.samples.TestXlet.perm
se per esempio una classe TestXlet fosse definita come appartenete ad un
ipotetico package samples.
Ogni elemento XML nel "Permission Request File" corrisponde ad una
specifica risorsa l’accesso alla quale viene richiesto per la relativa applicazione.
Segue un esempio di codice XML atto a richiedere l’accesso al filesystem
persistente del terminale MHP:
<?xml version="1.0"?>
<!DOCTYPE permissionrequestfile PUBLIC "-//DVB//DTD Permission
Request File
1.0//EN"
"http://www.dvb.org/mhp/dtd/permissionrequestfile1-0.dtd">
<permissionrequestfile orgid=”0x00000000” appid=”0x4004”>
<file value=”true”>
</file>
</permissionrequestfile>
In cui ovviamente l’organization id è scelto arbitariamente , tanto quanto
l’application id posto che quest’ultimo è stato scelto nel range 4000 -7FFF.
Valori da sostituire con quelli opportuni della specifica organizzazione ed
applicazione in una situazione di implementazione concreta.
Infine, l’ultimo dei requisiti che la specifica MHP impone affinché una Xlet
possa accedere al fileysistem persistente è che essa (nella forma dei file di
bytecode che la compongono presenti nel file system broadcast implementato
dall DSM-CC Oject Carousel) sia autenticata tramite firma digitale secondo le
modalità esposte al punto 12.2 della specifica 1.0.2.
35
Dal punto di vista del codice della Xlet, i permessi sono rappresentati dalle
sottoclassi di java.security.Permission. Ogni sottoclasse rappresenta
una specifica risorsa a cui una applicazione MHP può ottenere accesso previo
soddisfacimento delle dette condizioni;
Il permesso di accesso al file system su dispositivo di memorizzazione
persistente del STB, è rappresentato dalla classe
Java.io.FilePermission. Quando ad una Xlet è concesso tale
permesso, essa può accedere al file system persistente tramite le note classi del
package java.io;
è consigliabile gestire l’I/O usando classi che
implementino operazioni bufferizzate, per ragioni di efficienza e reattività
dell’applicazione; a meno che la relativa scarsità di memoria non diventi un
fattore limitante prima ancora dell’impiego effettivo più frequente del minimo
inevitabile delle risorse per l’ I/O su filesystem persistente, anche in presenza di
buffer di dimensioni molto limitate (tipicamente le classi del package java.io
utilizzano un buffer di default di 512 byte).
La directory radice della parte di filesystem persistente a cui si può accedere
può essere ottenuta dalla Xlet tramite la proprietà di sistema
dvb.persistent.root; ovvero:
System.getProperty("dvb.persistent.root")
(questa directory non è solitamente la radice del file system persistente ma solo
del sottoalbero che il sistema del STB riserva allo storage non volatile delle
applicazioni MHP).
L’Applicazione ha accesso solo a questa directory ed a sue subdirectory nei
limiti in seguito esposti; a tale directory base si può accedere solo in lettura da
una Xlet. Cercare di accedere ad una directory di livello superiore risulta in una
eccezione di tipo java.lang.SecurityException.
Una volta che una Xlet ottiene accesso all’area di storage persistente,
l’implementazione del middleware MHP crea automaticamente le seguenti
sottodirectory della citata directory base:
<organization_id>
<organization_id><file.seperator><application_id>
Dove <organization_id> ed <application_id> sono stringhe che
rappresentano i valori esadecimali specificati nella AIT per questi due
identificatori, a meno dei caratteri “0x” iniziali. Esse possono essere ottenute
tramite le proprietà di nome dvb.org.id e dvb.app.id da passare come
stringhe al metodo getXletProperty della classe XletContext; ogni
Xlet dispone di una istanza di tale classe che viene fornita dall’ambiente di
esecuzione in fase di inizializzazione dell’applicazione; tale istanza viene
36
passata al metodo initXlet che una Xlet deve implementare, come da
specifiche. In breve, sia xc un oggetto di tipo XletContext:
xc.getXletProperty(“dvb.org.id”)
xc.getXletProperty(“dvb.app.id”)
(Il metodo getXletProperty restituisce la stringa costituente l’ Id richiesto)
Con <file.seperator> si è prevedibilmente indicato il carattere (o la
stringa) che lo specifico ambiente di esecuzione usa per separare nomi di
directory e/o files all’interno di un path; solitamente esso non è diverso da
quello tipico dei sistemi unix o unix-like, “/” o al limite dei sistemi windows,
“\”. Può comunque per opportuna generalità del codice essere ottenuto tramite la
system property file.seperator, ovvero:
System.getProperty("file.separator")
La directory <organization_id> ha come proprietario il sistema operativo del
STB ed deve essere accessibile in lettura e scrittura per le applicazioni della
segnalate come appartenenti alla relativa organizzazione, accesibile in sola
lettura per qualunque applicazione possa accedere allo storage persistente.
La directory <application_id> è proprietà della specifica Xlet che ovviamente
può accedere sia in lettura che scrittura; alle altre applicazioni non è consentito
alcun accesso a meno che quella proprietaria non lo conceda tramite le
opportune modalità.
Le seguenti linee di codice mostrano come accedere da una Xlet ad un file nella
directory di proprietà di questa nel file system persistente, posto che
l’applicazione in questione abbia ottenuto il permesso di accesso a tale risorsa:
// stringa rappresentante il path del file a cui si vuole
// accedere
String filePath = new String();
// Costruzione del path completo ottenendo I valori delle
// opportune proprietà di sistema e del contesto di esecuzione
// della Xlet; sia xc un oggetto di tipo XletContext.
filePath
filePath
filePath
filePath
filePath
=
=
=
=
=
filePath.concat(System.getProperty("dvb.persistent.root"));
filePath.concat(System.getProperty("file.separator"));
filePath.concat(xc.getXletProperty("dvb.org.id"));
filePath.concat(System.getProperty("file.separator"));
filePath.concat(xc.getXletProperty("dvb.app.id"));
filePath = filePath.concat(System.getProperty("file.separator"));
filePath = filePath.concat ("testFile.txt");
37
// si crea un oggetto di tipo File che permette le comuni
// operazioni sul file identificato dal path precedentemente
// definito.
java.io.File testFile = new java.io.File(filePath);
Una applicazione MHP può settare sui file per cui ha accesso in scrittura vari
attributi relativi alla durata della disponibilità del file (per il quale può essere
definita una “data di scandenza”), priorità e permessi secondo un modello
ispirato a quello unix tradizionale. Tali proprietà permettono di controllare
l’accesso ad un file ed il suo “ciclo di vita” nel file system persistente.
Le seguenti linee di codice mostrano come settare e controllare i permessi per un
dato file, secondo quanto previsto dalle api java standard ed incluso in MHP. Le
azioni sul file che possono essere permesse o meno sono lettura, scrittura,
cancellazione ed esecuzione settabili tramite i relativi parametri read, write,
delete ed execute, che possono essere passati nella medesima stringa,
separati da virgola, come secondo argomento al costruttore di
java.io.FilePermission.
Il permesso di esecuzione è relativo alla possibilità di eseguire il file in
questione come formato binario eseguibile dello specifico sistema operativo,
tramite ad esempio java.lang.Runtime.exec(String filePath)
// la creazione di questo oggetto con i parametri dati
// permette lettura e scrittura sul file identificato dalla
// stringa filePath
java.io.FilePermission testPermission =
new java.io.FilePermission(filePath, "read,write");
// il metodo qui invocato ritorna (void) normalmente se i
// permessi settati in testPermission sono effettivamente
// ottenibili, altrimenti lancia l’eccezione intercettata
try{
java.security.AccessController.checkPermission(testPermission);
}
catch(AccessControlException ace){
System.out.println("checkPermission throws: " + ace.getMessage());
}
Da notare che le classi FilePemission ed AccessController sono mutuate dalle
API Java standard 1.2, non erano presenti nella version 1.1.8 da cui MHP eredita
la grande maggioranza delle API di base ma sono state comunque incluse nella
specifica.
Queste API permettono di modificare i permessi di accesso rispetto a quelle
che sono le impostazioni di default con cui viene inizializzata una xlet solo
relativamente ai parametri in comune con un ambiente java standard, per una
gestione completa degli attributi e permessi di acceso per un file in ambiente
MHP, sono state definite delle API apposite con le classi
38
org.dvb.persistent.FileAccessPemission
org.dvb.persistent.FileAttributes.
e
La classe FileAccessPermission definisce un tipo di oggetto che rappresenta la
serie di permessi read/write su un dato file, per le “utenze” world, organizzation
ed owner: con il termine world ci si riferisce a tutte le applicazioni che hanno
accesso allo storage persistente, con organization a tutte le xlet segnalate con il
medesimo organization id, e con owner alla xlet che ha creato il file.
Tali permessi in un oggetto di tipo FileAccessPermission vengono settati con sei
parametri booleani che indicano tre coppie di permessi read/write (in
quest’ordine) per world, organization ed owner (in quest’ordine): ciò avviene
tramite il costruttore o un apposito metodo se si vogliono settare i permessi su
un oggetto già istanziato. Tale classe implementa anche una serie di metodi che
permettono di controllare come sono settati i permessi in uno specifico oggetto
istanza di essa.
Questa è la definizione del costruttore:
public FileAccessPermissions(boolean
boolean
boolean
boolean
boolean
boolean
readWorldAccessRight,
writeWorldAccessRight,
readOrganisationAccessRight,
writeOrganisationAccessRight,
readApplicationAccessRight,
writeApplicationAccessRight)
e la seguente quella del citato metodo per settare i permessi:
public void setPermissions(boolean
boolean
boolean
boolean
boolean
boolean
ReadWorldAccessRight,
WriteWorldAccessRight,
ReadOrganisationAccessRight,
WriteOrganisationAccessRight,
ReadApplicationAccessRight,
WriteApplicationAccessRight)
Un oggetto di tipo FileAccessPermission può poi essere associato ad un file
tramite gli opportuni metodi static della classe FileAttributes, ad esempio, se “f”
è un oggetto di tipo File:
FileAccessPermissions fAP = new FileAccessPermissions(false, false, true,
false, true, true);
FileAttributes fA = FileAttributes.getFileAttributes(f);
fA.setPermissions(fAP);
FileAttributes.setFileAttributes(fA, f);
39
La classe FileAttributes permette inoltre di gestire anche gli altri attributi di un
file nel file system persistente, ovvero “priority” ed “expiration date;
analogamente a quanto già visto, si può ottenere un oggetto di tipo
FileAttributes che rispecchia gli attributi per un dato file con il metodo
getFileAttributes(File f), ottenere/modificare con gli appositi
metodi set/get i valori degli attributi ed infine ri-associare l’oggetto di tipo
FileAttributes così modificato al file su cui si vuole intervenire, tramite il
metodo setFileAttributes(FileAttributes fa, File f).
Ovviamente una Xlet potrà modificare attributi e permessi solo per file su cui ha
accesso in scrittura, quindi potrà eventualmente ad un certo momento solo
restringere le possibilità del suo stesso codice e/o rendere disponibili ad altre
applicazioni risorse che inizialmente non lo erano.
Infine si deve rilevare che anche lo storage persistente, in un terminale MHP non
è tale in termini assoluti: la specifica MHP definisce alcune regole in base alle
quali dei file nell’area di storage persistente riservata alle applicazioni possono
essere rimossi dal sistema. Questo perché tipicamente la risorsa fisica per la
memorizzazione persistente è tutt’altro che sovrabbondante soprattutto a lungo
termine e si sono voluti evitare scenari in cui una singola applicazione la
“monopolizzi” o in cui comunque vada ad esaurirsi; motivo per il quale tra gli
attributi di un file vi è una “expiration date”, settabile da una xlet che ne abbia
diritto, ma la cui impostazione ultima spetta comunque al sistema in caso di
necessità. Un file nel file system persistente può essere quindi definitivamente
rimosso quando:
- non c’è abbastanza spazio disponibile per le necessità di una xlet in
esecuzione;
- il file è “scaduto”;
- una xlet con permessi appropriati ha “eliminato” il file tramite la apposita api;
- il file risulta da eliminare da esplicito comando dell’utente;
- la capacità di memorizzazione dedicata alle applicazioni MHP risulta usata per
oltre il 75% della sua dimensione totale.
In caso di necessità di liberare spazio, viene prevedibilmente data precedenza
alla rimozione dei file “scaduti” e/o di cui è stata richiesta l’eliminazione.
La specifica MHP impone che la capacità di memorizzazione dedicata alle
applicazioni sia almeno di 4096 byte, implicando quindi una dimensione
garantita in generale alquanto restrittiva.
40
4.3 - Classe DataRetriever
In base alle considerazioni esposte in particolare nei due precedenti paragrafi ,si
propone una implementazione dimostrativa di una classe che fornisca un
possibile modello di oggetto atto ad acquisire dati da connessione RS-232,
tramite l’esecuzione di comandi si sistema dello specifico STB (attraverso il
metodo java,.lang.runtime.exec(String) ) ed a memorizzarli in un
file nel dispositivo per la memorizzazione persistente del STB.
In caso di esecuzione di codice basato su tale esempio, si dovrà garantire alla
Xlet l’accesso al filesystem persistente,
ovvero, in breve, segnalare
l’applicazione con application id nell’opportuno range e fornire un “permission
request file” nella corretta posizione nell’object carousel, come precedentemente
esposto. Riepilogando quindi, application id nel range 4000 -7FFF e permission
request file” del tipo:
<?xml version="1.0"?>
<!DOCTYPE permissionrequestfile PUBLIC "-//DVB//DTD Permission
Request File
1.0//EN"
"http://www.dvb.org/mhp/dtd/permissionrequestfile1-0.dtd">
<permissionrequestfile orgid=”0x00000000” appid=”0x4004”>
<file value=”true”>
</file>
</permissionrequestfile>
Si ricorda inoltre che una applicazione per poter accedere al filesystem
persistente dovrebbe essere autenticata tramite firma digitale secondo le
modalità esposte al punto 12.2 della specifica MHP. Sono tuttavia note
difformità di implementazione e comportamento di molti STB relativamente a
questo aspetto, essendo stati rilevati casi in cui poste in essere le altre condizioni
descritte, l’accesso viene garantito anche in assenza di firma digitale.
Segue il codice java per ambiente MHP, ampiamente commentato, relativo alla
classe poco sopra introdotta:
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché per esempio con "java.io.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre quelle del package
* java.lang, sempre comunque importate implicitamente in automatico).
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
41
/*
* Classe dimostrativa di una possibile struttura di un oggetto che implementi
* l'acquisizione dati da interfaccia RS-232 del STB, presumibilmente tramite
* esecuzione di uno o più comandi di sistema, data l'assenza di una api
* apposita per la gestione della detta connessione se presente nel terminale
* mhp. Si suppone l'applicazione abbia accesso al filesystem persistente,
* dato che la presente classe si occupa anche di memorizzare i dati acquisiti
* in un file il cui oggetto rappresentativo è passato al costruttore; un
* apposito metodo legge inoltre i dati da tale file, se questi non sono già
* stati copiati in memoria da una precedente chiamata o se nel mentre non è
* avvenuta una nuova acquisizione.
* L'acquisizione dei dati viene solo simulata in quanto una implementazione
* completamente effettiva dei compiti descritti non può non essere ad un
* qualche livello strettamente dipendente dallo specifico STB e relativo s.o.
*/
public class DataRetriever implements DataRetrieverInterface {
/*
* Costante da cui ricavare i byte di cui si simula l'acquisizione
*/
private final String FAKEDATA = "Dati acquisisti da RS-232";
/*
* Codifica di carattere con cui tradurre in una sequenza byte la stringa di
* cui sopra.
* canonical name: UTF-8
* descrption
: Eight-bit Unicode (or UCS) Transformation Format
*/
private final String FAKEDATA_CHARENC = "UTF8";
/*
* Dimensione in numero di byte per un buffer temporaneo in cui verrà letto
* il contenuto del file in cui sono stati acquisiti i dati.
*/
private final int DATABUFFER_DIM = 4096;
private final String SYSPROPNAME_APPSPERSISTENTROOT = "dvb.persistent.root";
private final String SYSPROPNAME_SEPARATOR = "file.separator";
private String sep;
/*
* Array di stringhe ognuna delle quali corrispondente ad un comando di
* sistema che possa essere utile a completare il compito di acquisizione
* dati da RS-232: una effettiva istanza di tale variabile sarà dipendente
* dallo specifico stb/s.o.
*/
private final String[] TARGETFILE_INDIPENDENT_SYSCOMMANDS = null;
/*
* Sequenza di comandi effettivamente da eseguire. tale array sarà
* istanziato da un apposito metodo, tenendo conto anche del file
* su cui si vogliono scrivere i dati acquisiti.
* una effettiva istanza di tale variabile sarà dipendente dallo specifico
* stb/s.o.
*/
private String[] sysCommandsToBeExecuted;
/*
* Stringa che rappresenta la directory "home" della applicazione nel
* filesystem persistente, in cui creare il file in cui vengono scritti i dati
* acquisiti. La specifica mhp definisce tale path essere del tipo
* <AppsStorageRoot>/<OrgId>/<AppId>.
*/
private String homePath;
/*
* Oggeto rappresentativo del file nel quale acquisire i dati
*/
private File targetFile;
42
/*
* Variabile atta a memorizzare i dati acquisiti previa lettura dal suddetto
* file ad ogni prima invocazione di getData() successiva ad una chiamata ad
* acquireData();
*/
private byte[] data;
/*
* Il costruttore si occupa di istanziare il separatore tra i nodi di un path,
* homePath, targetFile.
* Indirettamente, preparare la sequenza di comandi di sistema da eseguire
* per l' acquisizione dati, la quale sarà con tutta probabilità ad un certo
* punto dipendente anche da targetFile;
*/
public DataRetriever(String orgId, String appId, String targetFileName) {
this.sep = System.getProperty(SYSPROPNAME_SEPARATOR);
this.homePath = System.getProperty(SYSPROPNAME_APPSPERSISTENTROOT) +
this.sep + orgId + this.sep + appId;
setTargetFile(targetFileName);
}
/*
* Istanzia targetFile ed tramite setupSysCommands() prepara la sequenza di
* comandi di sistema da eseguire per l' acquisizione dati, la quale sarà conù+
* tutta probabilità ad un certo punto dipendente anche da targetFile;
*/
public void setTargetFile(String targetFileName) {
this.targetFile = new File(this.homePath, targetFileName);
setupSysCommands();
}
/*
* Prepara la sequenza di comandi di sistema da eseguire per l' acquisizione
* dati, la quale sarà con tutta probabilità ad un certo punto dipendente
* anche da targetFile, oltre che ovviamente dallo specifico stb/s.o.
*/
private void setupSysCommands() {
// implementazione dipendente dallo specifico stb/s.o.
}
/*
* Metodo destinato ad implementare l'acquisizione dati da RS-232 del stb.
* Si suppone in prima istanza di poter allo scopo usare il metodo
* exec(String) o uno analogo o una sequenza di essi della classe
* java.lang.Runtime, per eseguire una serie di opportuni comandi di sistema
* possibile però che le impostazioni di sicurezza di default dello specifico
* ambiente mhp impediscano tale esecuzione; la specifica mhp non da direttive
* generali esplicite su questo aspetto.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* utile a facilitare il testing.
* L'attuale implementazione simula solamente l acquisizione dei dati tramite
* il metodo di comodo pretend();
*/
public boolean acquireData() {
try {
// esecuzione dei comandi di sistema rappresentati dalla serie di stringhe
// in sysCommandsToBeExecuted; il relativo codice è disattivato in quanto
// l'acquisizione dati in questa implementazioen è solo simulata, tramite
// il metodo pretend().
// for (int i=0; i<sysCommandsToBeExecuted.length; i++) {
//
Process p = Runtime.getRuntime().exec(sysCommandsToBeExecuted[i]);
//
p.waitFor();
// }
pretend();
this.data = null;
return true;
}
43
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}
/*
* Alla prima invocazione successiva ad una acquisizione dati, legge i medesimi
* da targetFile, li memorizza nella variabile di classe data e li restituisce
* come array di byte. restituisce i dati già memorizzati quando tra la
* precedente e la presente chiamata non è avvenuta una nuova acquisizione.
* restituisce null se il file da leggere non esiste o se si verifica una
* eccezione.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* utile a facilitare il testing.
*/
public byte[] getData() {
if (this.data != null) {
return this.data;
}
try {
if (targetFile.exists() == false ) {
return null;
}
FileInputStream fIS = new FileInputStream(this.targetFile);
byte[] buff = new byte[DATABUFFER_DIM];
int readBytesNum = fIS.read(buff);
fIS.close();
byte[] data = new byte[readBytesNum];
for (int i=0; i<data.length; i++) {
data[i] = buff[i];
}
this.data = data;
return data;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return null;
}
}
/*
* Simula l'acquisizione dati da RS-232 scrivendo in targetFile
* dati già disponibili come costante di classe.
*/
private void pretend() throws Exception {
byte[] data = FAKEDATA.getBytes(FAKEDATA_CHARENC);
// la home directory in mhp è creata automaticamente dal sistema per
// ogni xlet secondo il path standard.
FileOutputStream fOS = new FileOutputStream(this.targetFile);
fOS.write(data);
fOS.flush();
fOS.close();
}
}
44
4.3.1 - Interfaccia DataRetrieverInterface
Come evidente dal relativo codice, la classe DataRetriever implementa
l’interfaccia DataRetrieverInterface; tale interfaccia definisce i tre
metodi pubblici di cui si sono esposti scopi ed una possibile implementazione in
DataRetriever; E’ stato scelto il paradigma interfaccia/implementazione per la
classe che definisce un tipo dedicato ad eseguire in primo luogo l’acquisizione
dei dati, tramite esecuzione di opportuno comandi nativi, in quanto la natura del
compito implica una forte dipendenza della implementazione da un specifico
modello di STB e relativo sistema operativo o comunque da specifici insiemi di
STB/s.o. definibili in base a produttore / modello / hardware utilizzato.
Il paradigma interfaccia/implementazione permette in caso di necessità di avere
diverse implementazioni di una classe che fornisca le funzionalità esposte per
DataRetriever, senza necessità di modificare il codice utilizzante tale classe (ad
eccezione eventualmente dell’istruzione che istanzia lo specifico oggetto, nel
caso si renda necessario avere classi implementanti DataRetrieverInterface che
abbiano costruttori diversamente definiti).
Stessa considerazione si può fare per il meccanismo dell’estensione, ma è stato
preferito quello interfaccia/implementazione in quanto la particolare natura dei
compiti previsti per una classe che implementi DataRetrieverInterface rende
probabili una sovrascrittura pressoché totale della classe estesa nel momento in
cui si abbia necessità di realizzare una differente implementazione per
dispositivi diversi da quelli cui era dedicata la classe estesa.
Segue il codice per la semplice interfaccia DataRetrieverInterface:
public interface DataRetrieverInterface {
/*
* Setta il nome del file su cui verranno scritti i dati acquisiti da rs-232;
* tale file verrà creato nella home della xlet creata automaticamente dal
* middleware il path standard <AppsStorageRoot>/<OrgId>/<AppId>.
*/
public void setTargetFile(String targetFileName);
/*
* Esegue le operazioni di acquisizione dati e scrittura dei medesimi sul
* file definito a tale scopo.
*/
public boolean acquireData();
/*
* Restituisce i dati acquisiti nell'ultima invocazione di
* acquireData() che ha preceduto quella che si opera a questo metodo.
*/
public byte[] getData();
}
45
4.3.2 - Classe DataRetrieverOsmosys
Si propone di seguito una diversa implementazione dell’interfaccia
DataRetrieverInterface, implementazione che riprende lo schema generale
mostrato in DataRetriever ma si basa su comandi di sistema propri di STB con
sistema operativo del produttore Osmosys, allo scopo di permettere una effettiva
acquisizione dei dati da connessione RS-232, posto che la specifica
implementazione dell’ambiente di esecuzione MHP conceda l’uso dei metodi di
java.lang.Runtime; ovviamente la seguente implementazione non è eseguibile,
non con risultati da attendersi a priori positivi almeno, su un qualunque STB
conforme allo standard MHP arbitrariamente scelto, ma necessita di un modello
con l’opportuno software di sistema. STB che soddisfano tale requisito sono
comunque piuttosto diffusi.
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché per esempio con "java.io.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre a quelle dell'
* eventuale package di appartenenza e quelle del package java.lang, sempre
* comunque importate implicitamente in automatico).
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
/*
* Classe dimostrativa di una possibile struttura di un oggetto che implementi
* l'acquisizione dati da interfaccia RS-232 del STB, presumibilmente tramite
* esecuzione di uno o più comandi di sistema, data l'assenza di una api
* apposita per la gestione della detta connessione se presente nel terminale
* mhp. Si suppone l'applicazione abbia accesso al filesystem persistente,
* dato che la presente classe si occupa anche di memorizzare i dati acquisiti
* in un file il cui oggetto rappresentativo è passato al costruttore; un
* apposito metodo legge inoltre i dati da tale file, se questi non sono già
* stati copiati in memoria da una precedente chiamata o se nel mentre non è
* avvenuta una nuova acquisizione.
* Questa implementazione è basata su comandi di sistema validi per stb aventi
* sistema operativo Osmosys.
*/
public class DataRetrieverOsmosys implements DataRetrieverInterface {
/*
* Dimensione in numero di byte per un buffer temporaneo in cui verrà letto
* il contenuto del file in cui sono stati acquisiti i dati. Variabile usata
* anche nel generare stringhe corrispondenti ai comandi di sistema per lo
* specifico stb/s.o necessari acquisire da rs-232 il numero di byte che il
* valore della stringa rappresenta per poi scriverli in un file nel
* filesystem persistente.
*/
private final int DATABUFFER_DIM = 4096;
private final String SYSPROPNAME_APPSPERSISTENTROOT = "dvb.persistent.root";
private final String SYSPROPNAME_SEPARATOR = "file.separator";
private String sep;
private final String TARGETFILE_PLACEHOLDER = "<TargetFile>";
private final String BYTESNUM_PLACEHOLDER = "<BytesNum>";
46
/*
* Array di stringhe ognuna delle quali corrispondente ad un comando di
* sistema che possa essere utile a completare il compito di acquisizione
* dati da RS-232: una istanza di tale costante sarà dipendente
* dallo specifico stb/s.o. L'array qui allocato contiene comandi di un
* s.o. Osmosys utili agli scopi enunciati.
*/
private final String[] TARGETFILE_INDIPENDENT_SYSCOMMANDS = {
"\\n\\n[1open(\"" + TARGETFILE_PLACEHOLDER + "\",0,0)\\n\\n",
"\\n\\n[6write(0," + BYTESNUM_PLACEHOLDER + ")\\n\\n",
"\\n\\n[3close(9175055)\\n\\n"
};
/*
* Sequenza di comandi effettivamente da eseguire. Tale array sarà
* istanziato da un apposito metodo, tenendo conto anche del file
* su cui si vogliono scrivere i dati acquisiti.
* Una istanza di tale variabile sarà dipendente dallo specifico
* stb/s.o.
*/
private String[] sysCommandsToBeExecuted;
/*
* Stringa che rappresenta la directory "home" della applicazione nel
* filesystem persistente, in cui creare il file in cui vengono scritti i dati
* acquisiti. La specifica mhp definisce tale path essere del tipo
* <AppsStorageRoot>/<OrgId>/<AppId>.
*/
private String homePath;
/*
* Oggetto rappresentativo del file nel quale acquisire i dati
*/
private File targetFile;
/*
* Variabile atta a memorizzare i dati acquisiti previa lettura dal suddetto
* file ad ogni prima invocazione di getData() successiva ad una chiamata ad
* acquireData();
*/
private byte[] data;
/*
* Il costruttore si occupa di istanziare il separatore tra i nodi di un path,
* homePath e targetFile.
* Indirettamente, prepara la sequenza di comandi di sistema da eseguire
* per l' acquisizione dati, la quale sarà con tutta probabilità ad un certo
* punto dipendente anche da targetFile;
*/
public DataRetrieverOsmosys(String orgId, String appId,
String targetFileName) {
this.sep = System.getProperty(SYSPROPNAME_SEPARATOR);
this.homePath = System.getProperty(SYSPROPNAME_APPSPERSISTENTROOT) +
this.sep + orgId + this.sep + appId;
setTargetFile(targetFileName);
}
/*
* Istanzia targetFile ed tramite setupSysCommands() prepara la sequanza di
* comandi di sistema da eseguire per l' acquisizione dati, la quale sarà con
* tutta probabilità ad un certo punto dipendente anche da targetFile;
*/
public void setTargetFile(String targetFileName) {
this.targetFile = new File(this.homePath, targetFileName);
setupSysCommands();
}
47
/*
* Prepara la sequenza di comandi di sistema da eseguire per l' acquisizione
* dati, la quale sarà con tutta probabilità ad un certo punto dipendente
* anche da targetFile, oltre che ovviamente dallo specifico stb/s.o.
*/
private void setupSysCommands() {
// (Implementazione in generale dipendente dallo specifico stb/s.o.)
this.sysCommandsToBeExecuted = new String[
TARGETFILE_INDIPENDENT_SYSCOMMANDS.length];
for (int i=0; i<this.sysCommandsToBeExecuted.length; i++) {
String command = TARGETFILE_INDIPENDENT_SYSCOMMANDS[i];
String sub1 = TARGETFILE_PLACEHOLDER;
String sub2 = BYTESNUM_PLACEHOLDER;
int index = command.indexOf(sub1);
if (index != -1) {
String pref = command.substring(0, index);
String suff = command.substring(index + sub1.length());
command = pref + this.homePath + this.sep + this.targetFile.getName() +
suff;
}
index = command.indexOf(sub2);
if (index != -1) {
String pref = command.substring(0, index);
String suff = command.substring(index + sub2.length());
command = pref + String.valueOf(this.DATABUFFER_DIM) + suff;
}
sysCommandsToBeExecuted[i] = command;
}
}
/*
* Metodo atto ad implementare l'acquisizione dati da RS-232 del stb.
* Si suppone in prima istanza di poter allo scopo usare il metodo
* exec(String) o uno analogo o una sequenza di essi della classe
* java.lang.Runtime, per eseguire una serie di opportuni comandi di sistema;
* possibile però che le impostazioni di sicurezza di default dello specifico
* ambiente mhp impediscano tale esecuzione; la specifica mhp non da direttive
* generali esplicite su questo aspetto.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* utile a facilitare il testing.
* L'attuale implementazione simula solamente l acquisizione dei dati tramite
* il metodo di comodo pretend();
*/
public boolean acquireData() {
try {
// Esecuzione dei comandi di sistema rappresentati dalla serie di stringhe
// in sysCommandsToBeExecuted
System.out.println();
for (int i=0; i<sysCommandsToBeExecuted.length; i++) {
System.out.println("Esecuzione del comando di sistema " +
sysCommandsToBeExecuted[i]);
Process p = Runtime.getRuntime().exec(sysCommandsToBeExecuted[i]);
p.waitFor();
System.out.println("Eseguito.");
System.out.println();
}
this.data = null;
return true;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}
48
/*
* Alla prima invocazione successiva ad una acquisizione dati, legge i medesimi
* da targetFile, li memorizza nella variabile di classe “data” e li restituisce
* come array di byte. Restituisce i dati già memorizzati quando tra la
* precedente e la presente chiamata non è avvenuta una nuova acquisizione.
* Restituisce null se il file da leggere non esiste o se si verifica una
* eccezione.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* utile a facilitare il testing.
*/
public byte[] getData() {
if (this.data != null) {
return this.data;
}
try {
if (targetFile.exists() == false ) {
return null;
}
FileInputStream fIS = new FileInputStream(this.targetFile);
byte[] buff = new byte[DATABUFFER_DIM];
int readBytesNum = fIS.read(buff);
fIS.close();
byte[] data = new byte[readBytesNum];
for (int i=0; i<data.length; i++) {
data[i] = buff[i];
}
this.data = data;
return data;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return null;
}
}
}
4.4 - Connettersi ad un ISP
Si espone il codice costituente una classe che permetta di stabilire una
connessione non permanente con un sistema remoto sulla base di numero
telefonico, nome utente e relativa password: ovvero, tipicamente quanto
necessario per connettersi ad Internet tramite un ISP.
4.4.1 - classe NetConnection
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché ad esempio con "org.dvb.net.rc.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre a quelle del package
* java.lang, sempre comunque importate implicitamente in automatico).
*/
import org.dvb.net.rc.RCInterfaceManager;
import org.dvb.net.rc.RCInterface;
import org.dvb.net.rc.ConnectionRCInterface;
import org.dvb.net.rc.ConnectionParameters;
import org.dvb.net.rc.PermissionDeniedException;
49
/*
* La seguente classe definisce un tipo di oggetto che si occupi di instaurare
* una connessione remota, tipicamente ad un ISP, o di verificare che esista una
* connessione di tipo always-on, per esempio una connessione ad una LAN che
* consenta l'accesso ad internet tramite gateway.
* Operazioni effettuate tramite le api definite dalla specifica mhp per la
* funzionalità "return channel".
*/
public class NetConnection {
/*
* Oggetto che permette, se necessario di gestire la connessione ad un sistema
* remoto tramite num. tel. / nome-utente / password; l'interfaccia
* ConnectionRCInterface è tipicamente usata per connettersi ad un ISP
*/
private ConnectionRCInterface myInterface;
/*
* Oggetto "contenitore" per i parametri di connessione
*/
private ConnectionParameters myConnectionParameters;
/*
* Il costruttore istanzia le variabili di classe myInterface e
* myConnectionParameters, a meno che non sia disponibile una connessione
* di rete di tipo permanente.
*/
public NetConnection(String phoneNum, String uN, String pW){
// Si crea una istanza dell'oggetto factory da cui ottenere
// oggetti le cui classi implementi le interfacce atte alla gestione di una
// connessione
RCInterfaceManager rcm = RCInterfaceManager.getInstance();
// Si ottiene un array di oggetti rappresentativi delle connessioni di rete
// dipsonibili.
RCInterface[] interfaces = rcm.getInterfaces();
//
//
//
//
//
//
//
//
//
if
Si deve ora controllare il tipo di interfacce di rete disponibili,
tipicamente su un comune stb ve ne è una sola di tipo dial-up con cui
connettersi eventualmente ad un ISP. In un stb conforme all "Interactive
broadcast profile" è garantita la presenza di almeno una connessione.
se un elemento di tipo RCInterface risulta essere istanza della più
specifica classe ConnectionRCInterface tale oggetto rappresenta una
connessione che deve essere gestita, appunto ad esempio una connessione
via modem. In caso contrario, significa che si dispone di una connessione
di tipo always-on.
(interfaces[0] instanceof ConnectionRCInterface) {
// Si istanzia l'oggetto utile a gestire la connessione
myInterface = (ConnectionRCInterface)interfaces[0];
// Stessa operazione per,l'oggetto atto a contenere i parametri
// per la connessione
myConnectionParameters = new ConnectionParameters(phoneNum, uN, pW);
}
else {
// Non c'è bisogno di alcuna azione in quanto si dispone
// di una connessione di rete permanente.
}
}
50
/*
* Metodo che instaura la connessione remota con i parametri passati
* ad un oggetto della presente classe tramite il costruttore.
* restituisce true se si effettua la connessione con successo o se
* si dispone di una connessione always-on.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* che faciliti il testing.
*/
public boolean connect() {
// se la variabile di istanza myInterface è null significa che
// che si dispone di una connessione always-on
if (this.myInterface == null) {
return true;
}
try {
// Si chiede di riservare l'interfaccia di rete alla Xlet
// in cui viene eseguito il presente codice;
myInterface.reserve(myInterface.getClient(), null);
// Quanto segue prepara l'oggetto myInterface ad effettuare la connessione
// al sistema remoto desiderato con gli opportuni parametri
myInterface.setTarget(myConnectionParameters);
// si instaura effettivamente la connessione
myInterface.connect();
return true;
// do whatever we want to now that we've got a
// connection
}
catch (PermissionDeniedException pde) {
// In questo caso il sistema non concede alla xlet di riservare
// l'interfaccia di rete
System.out.println(pde.getMessage());
pde.printStackTrace();
return false;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}
51
/*
* Metodo che chiude la connessione e rilascia le relative risorse.
* Restituisce true se tali operazioni terminano con successo, false
* in caso di eccezione.
* Eventuali eccezioni vengono gestite con feedback sullo standard output
* che faciliti il testing.
*/
public boolean disconnect() {
// Se la variabile di istanza myInterface è null significa che
// che si dispone di una connessione always-on che non deve essere chiusa.
if (this.myInterface == null) {
return true;
}
try {
if (myInterface.isConnected() == true) {
myInterface.disconnect();
}
myInterface.release();
return true;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}
}
4.5 - Invio dei Dati ad Un Sistema Remoto Tramite TCP/IP
Il presente paragrafo fornisce alcuni esempi di codice che hanno come target un
STB quantomeno conforme a MHP 1.0.x Interactive Broadcast Profile (e
nell’ipotesi già assunta di lavorare con un STB di sviluppo o terminale
equivalente per funzionalità fornite, tale conformità si può sostanzialmente dare
per scontata in quanto il profilo in questione è implementato nella pressoché
totalità dei STB o dispositivi equivalenti attualmente diffusi), in quanto appunto
incentrati sull’uso, tramite le opportune interfacce di programmazione standard,
di quelle quelle funzionalità che la specifica MHP include per l’uso di una
connessione di rete. A livello hardware la più comune interfaccia di rete
attualmente implementata in un STB MHP è un modem PSTN.
In particolare, il codice proposto intende essere utile ai seguenti scopi:
- instaurare una connessione di livello tcp/ip tra il terminale MHP ed il sistema
remoto di destinazione, sulla base di indirizzo ip del medesimo e numero della
porta su cui sia “in ascolto” il componente software a cui ci si deve connettere;
- inviare i dati precedentemente acquisiti tramite la connessione tcp/ip stabilita.
52
Negli esempi proposti si suppone che le informazioni necessarie
all’identificazione della destinazione remota cui inviare i dati ed i dati acquisiti
stessi siano disponibili in memoria sotto forma di un qualche opportuno tipo di
oggetto Java, tipicamente String o per quel che riguarda i dati acqusiti tramite
RS-232 anche char[] o byte[].
Inoltre ci sia è limitati a considerare una connessione di livello tcp/ip in quanto
la disponibilità di una implementazione di tale protocollo nel middleware è
obbligatoria in qualunque profilo MHP che includa le funzionalità di return
channel (ovvero tutti tranne quello basilare per interattività solo locale), mentre
così non è per altri protocolli di uso comune; ad esempio l’implementazione di
HTTP 1.0 è molto comune anche in STB che comunque per il resto sono
comformi solo ad MHP 1.0.x Interactive Broadcast Profile, ma non essendo
HTTP 1.0 imposto per la conformità a tale profilo, si è preferito per maggiore
generalità non prendere in considerazione tale opzione, anche perché una
connessione di livello tcp/ip risulta sufficiente quantomeno per gli obiettivi
minimi.
4.5.1 - Classe TCPClient
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché per esempio con "java.io.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre quelle del package
* java.lang, sempre comunque importate implicitamente in automatico).
*/
import java.net.Socket;
import java.net.UnknownHostException;
import
import
import
import
java.io.DataOutputStream;
java.io.PrintWriter;
java.io.BufferedReader;
java.io.IOException;
/*
* Questa classe implementa un client di rete secondo
* un modello basato sull'utilizzo della classe Socket che
* permette di stabilire la connessione di rete di livello tcp e scambiare dati
* attraverso di essa con la controparte remota, dati nome o indirizzo ip
* dell'host e porta su cui si assume un opportuno software sia in esecuzione
* ed "ascolto".
* Presenta un'impostazione di validità abbastanza generale tale che,
* in una re-implementazione in cui usando la connessione per un compito diverso,
* la nuova classe possa essere sottoclasse di questa sovrascrivendo con
* modifiche sostanziali i soli metodi "handleConnection".
* Si usa SocketIOUtil appositamente definita per la creazione degli oggetti che
* forniscono i flussi di I/O , in modo da semplificare il relativo codice
* nei metodi "handleConnection" e rendere l'insieme sufficientemente modulare.
*/
public class TCPClient {
53
/*
* Costante che contiene il nome della codifica di carattere
* da utilizzare se l'informazione da inviare è di tipo testuale e si vuole
* gestire invio (ed eventualmente ricezione lato server) direttamente come
* stringa o sequenza di caratteri.
* Da passare come argomento charEnc1 al metodo
* handleConnection(String s, String charEnc1, String charEnc2).
*/
public static final String DATA_CHARENC = SocketIOUtil.UTF_8;
/*
* Costante che contiene il nome della codifica di carattere da utilizzare
* per interpretare i byte eventualmente ricevuti dal sistema remoto come
* conferma dell'invio dati, supponendo si tratti appunto di una sequenza di
* caratteri con codifica nota.
* Da passare al come secondo argomento al metodo
* handleConnection(byte[] bA, String charEnc), o come terzo all'omologo con
* tre argomenti.
*/
public static final String SERVERMSG_CHARENC = SocketIOUtil.UTF_8;
/*
* num. di char >= a quello effettivo del messaggio di conferma da parte
* del server
*/
protected static final int SERVERMSG_BUFFLENGTH = 1000;
protected static final String SYSDEF_LINESEP = System.getProperty(
"line.separator");
/*
* Il valore della variabile host può essere sia il nome di dominio che
* l'indirizzo IP stesso del sistema target.
*/
protected String host;
protected int port;
/*
* Oggetto che implementa la connessione TCP
*/
protected Socket socketConn = null;
/*
* Il costruttore registra, inizializzando le relative variabili di classe,
* l' host e la porta su cui si presume in ascolto il server ed ai quali si
* tenterà la connessione.
* La connessione non viene effettivamente stabilita finché non viene
* chiamato il metodo connect().
*/
public TCPClient(String host, int port) {
this.host = host;
this.port = port;
}
/*
* tipico metodo getter per la variabile di istanza host
*/
public String getHost() {
return new String(host);
}
/*
* tipico metodo getter per la variabile di istanza port
*/
public int getPort() {
return port;
}
54
/*
* tipico metodo setter per la variabile di istanza host
*/
public void setHost(String host) {
this.host = host;
}
/*
* tipico metodo setter per la variabile di istanza port
*/
public void setPort(int port) {
this.port = port;
}
/*
* connect() stabilisce la connessione istanziando la variabile di istanza
* socketConn di tipo Socket.
* Se un connessione è già in essere, ci si assicura di chiuderla ed una volta
* chiusa di rendere disponibile il relativo oggetto al garbage collector; se
* la chiusura non ha successo, la relativa eccezione di tipo IOException
* viene gestita con un feedback sullo standard output che faciliti il testing
* In caso di chiusura fallita si ritiene più opportuno non dereferenziare
* l'oggetto di tipo Socket, in modo che nell'utilizzo della presente classe
* ed in una tale eventualità rimanga evidente l'anomalia verificatasi e non
* si abbiano un oggetto Socket con connessione attiva e, con tutta
* probabilità, un I/O stram associato che impegnano risorse ma non più
* referenziabili.
* Analogamente viene gestita la possibile eccezione UnknownHostException
* se dal nome dell'host non risulta determinabile un indirizzo IP, così come
* eventuali altre, intercettate genericamente, possibili in caso sia attivo
* un security manager nello specifico ambiente di esecuzione.
* connect() restituisce "true" se solo se la connessione è stata stabilita.
*/
public boolean connect() {
try {
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " ...");
if(socketConn != null) {
socketConn.close();
}
socketConn = null;
socketConn = new Socket(host, port);
System.out.println("Connessione riuscita");
System.out.println();
return true;
}
//gestione di eventuali eccezioni, con feedback sullo standard output
catch(UnknownHostException uhe) {
//socketConn = null; // superfluo
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " fallita.");
System.out.println("UnknownHostException: " + uhe.getMessage());
uhe.printStackTrace();
return false;
}
catch(IOException ioe) {
//socketConn = null; // se la chiusura della connessione non ha successo
// meglio non perdere il riferimento all'oggetto
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " fallita.");
System.out.println("IOException: " + ioe.getMessage());
ioe.printStackTrace();
return false;
}
catch(Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
return false;
}
}
55
/*
* disconnect() chiude una connessione tcp attiva invocando l'apposito metodo
* close() sulla variabile di istanza socketConn di tipo Socket; quindi rende
* disponibile il relativo oggetto al garbage collector.
* Se la chiusura non ha successo, la relativa eccezione di tipo IOException
* viene gestita con un feedback sullo standard output che faciliti il testing
* In caso di chiusura fallita si ritiene più opportuno non dereferenziare
* l'oggetto di tipo Socket, in modo che nell'utilizzo della presente classe
* ed in una tale eventualità rimanga evidente l'anomalia verificatasi e non
* si abbiano un oggetto Socket con connessione attiva e, con tutta
* probabilità, un I/O stream associato che impegnano risorse ma non più
* referenziabili.
* Restituisce true se la chiusura della connessione ha avuto successo,
* false altrimenti.
*/
public boolean disconnect() {
if(socketConn == null) {
return true;
}
try {
socketConn.close();
socketConn = null;
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " chiusa.");
return true;
}
//gestione di eventuali eccezioni, con feedback sullo standard output
catch(IOException ioe) {
//socketConn = null; // se la chiusura della connessione non ha successo
// meglio non perdere il riferimento all'oggetto
System.out.println("Chiusura connessione tcp all'host " + host +
" porta " + String.valueOf(port) + " fallita.");
System.out.println("IOException: " + ioe.getMessage());
ioe.printStackTrace();
return false;
}
catch(Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
return false;
}
}
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Il seguente metodo utilizza una connessione tcp già stabilita per inviare
i dati ad passati in forma binaria tramite il primo argomento.
Restituisce una stringa corrispondente alla risposta ricevuta dal sistema
remoto, che si suppone essere un messaggio di conferma nella forma di una
sequenza di caratteri la cui codifica binaria verrà interpretata secondo
dall' oggetto per la gestione del flusso dati in input, secondo la codifica
di carattere corrispondente al nome passato al presente metodo come
secondo argomento. Nel caso non sia inviato nulla in risposta, l'assenza di
input causa la restituzione della stringa vuota.
Restituisce effettivamente un oggetto String nel caso l'invio dei dati e la
ricezione del messaggio di conferma abbiano successo; restituisce null
altrimenti.
Vengono gestite le eventuali eccezioni possibili nelle operazioni di output
ed input con feedback sullo standard output che faciliti il
testing, così come viene fornito un minimo di feedback sulla medesima
destinazione anche in caso di esecuzione regolare, principalmente per la
medesima ragione.
56
* La chiusura di un flusso di ouput od input invocando il metodo close()
* sull'opportuno oggetto (ad esempio in questo metodo gli oggetti di tipo
* DataOutputStream o BufferedReader), quando questo è, come nel presente caso,
* collegato alla connessione tcp implementata dall'oggetto di tipo Socket
* provoca la terminazione della connessione stessa con effetti pari all'
* invocazione del metodo close() della classe Socket. Simmetricamente, la
* la chiusura della connessione provoca quella degli I/O stream collegati.
* Per tale ragione in questo metodo non si invoca il metodo close() sugli
* oggetti usati per le operazioni di I/O, demandandone i medesimi effetti e
* e rilascio di risorse alla invocazione del metodo disconnect() da parte del
* codice utilizzante oggetti della presente classe; ciò permette anche di
* poter compiere più chiamate al metodo handleConnection senza dover ogni
* volta stabilire una nuova connessione.
* handleConnection(byte[] bA, String charEnc) è un metodo che sarà opportuno
* sovrascrivere in una eventuale estensione della presente classe, allo scopo
* di operare un differente uso della connessione tcp.
*/
public String handleConnection(byte[] bA, String charEnc) {
//si verifica che una connessione sia stata instaurata.
if(socketConn == null) {
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " non disponibile.");
return null;
}
try {
// si crea un oggetto idoneo all'invio dei dati in formato binario,
// tramite la classe SocketIOUtil appositamente definita.
// Tale oggetto gestirà un flusso di output attraverso la connessione tcp
// implementata dalla variabile di classe socketConn.
DataOutputStream out = SocketIOUtil.getBinaryWriter(socketConn);
// Invio dei dati
out.write(bA, 0, bA.length);
// Ci si assicura che un eventuale buffer in uscita sia svuotato e tutti
// i byte effettivamente inviati a destinazione.
out.flush();
// Un minimo di feedback su quanto inviato, funge anche da
// conferma che l'invio ha avuto successo.
String sentBytes = "";
for (int i=0; i<bA.length; i++) {
sentBytes = sentBytes + new Byte(bA[i]).toString() + " ";
}
System.out.println("Numero di byte inviati
: " +
String.valueOf(bA.length));
System.out.println("Byte inviati (corrispondenti valori decimali) : " +
sentBytes);
System.out.println();
// Si crea un oggetto idoneo alla ricezione di informazione di tipo
// testuale tramite la classe SocketIOUtil appositamente
// definita; Tale oggetto gestirà un flusso di output attraverso la
// connessione tcp implementata dalla variabile di classe socketConn.
// I byte in input saranno tradotti in caratteri secondo la
// codifica di carattere corrispondente al nome dato dal valore di charEnc
BufferedReader in = SocketIOUtil.getReader(socketConn, charEnc);
57
// Ricezione del messaggio di conferma atteso dalla controparte remota.
// Il metodo BufferedReader.read(chat[] cbuff, int offset, int length)
// itera la lettura di caratteri finché non vengono letti (e memorizzati in
// cbuff)length caratteri, fino ad un EOF o finché la successiva lettura
// non risulterebbe bloccante causa input stream non pronto alla lettura.
// Tale metodo restituisce -1 se lo stream restituisce un EOF (quando
// viene raggiunto il termine del flusso di input) al primo tentativo di
// lettura, altrimenti restituisce il numero di caratteri letti.
// Ovviamente si istanzia il buffer cbuff con una dimensione che si
// possa ragionevolmente supporre maggiore od uguale al numero di char
// che compongono al risposta del server.
// Tutti i caratteri letti vengono memorizzati in una stringa per un
// agevole feedback ed affinché tale stringa venga poi restituita dal
// presente metodo.
char[] cBuff = new char[SERVERMSG_BUFFLENGTH];
int readCharsNum = in.read(cBuff, 0, cBuff.length);
String recievedString = "" + new String(cBuff, 0, readCharsNum);
// Un minimo di feedback su quanto ricevuto, funge anche da
// conferma che la ricezione ha avuto successo.
System.out.println("
[stringa ricevuta]" + recievedString +
"[/stringa ricevuta]");
System.out.println("Numero di caratteri
: " + String.valueOf(
recievedString.length()));
System.out.println("Codifica di carattere : " + charEnc);
System.out.println("Numero di byte
: " + String.valueOf(
recievedString.getBytes(charEnc).length));
System.out.println();
//socketConn.close(); //debug
return recievedString;
}
//gestione di eventuali eccezioni, con feedback sullo standard output
catch(IOException ioe) {
System.out.println("IOException: " + ioe.getMessage());
ioe.printStackTrace();
// la restituzione di null segnala che le operazioni di invio dati
// e ricezione di conforma sono fallite.
return null;
}
catch(Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
// la restituzione di null segnala che le operazioni di invio dati
// e ricezione di conferma sono fallite.
return null;
}
}
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Il seguente metodo utilizza una connessione tcp già stabilita per inviare
i dati ad esso passati in forma testuale tramite il primo argomento.
Tale stringa verrà codificata in una sequenza di byte dall'oggetto per la
gestione del flusso dati in input, secondo la codifica di carattere
corrispondente al nome passato al presente metodo come secondo argomento.
Restituisce una stringa corrispondente alla risposta ricevuta dal sistema
remoto, che si suppone essere un messaggio di conferma nella forma di una
sequenza di caratteri la cui codifica binaria verrà interpretata
dall'oggetto per la gestione del flusso dati in input, secondo la codifica
di carattere corrispondente al nome passato al presente metodo come
terzo argomento. Nel caso non sia inviato nulla in risposta, l'assenza di
input causa la restituzione della stringa vuota.
Restituisce effettivamente un oggetto String nel caso l'invio dei dati e la
ricezione del messaggio di conferma abbiano successo; restituisce null
altrimenti.
58
* Vengono gestite le eventuali eccezioni possibili nelle operazioni di output
* ed input con feedback sullo standard output che faciliti il
* testing, così come viene fornito un minimo di feedback sulla medesima
* destinazione anche in caso di esecuzione regolare, principalmente per la
* medesima ragione.
* La chiusura di un flusso di ouput o input invocando il metodo close()
* sull'opportuno oggetto (ad esempio in questo metodo gli oggetti di tipo
* PrintWriter o BufferedReader), quando questo è, come nel presente caso,
* collegato alla connessione tcp implementata dall'oggetto di tipo Socket
* provoca la terminazione della connessione stessa con effetti pari all'
* invocazione del metodo close() della classe Socket. Simmetricamente, la
* la chiusura della connessione provoca quella degli I/O stream collegati.
* Per tale ragione in questo metodo non si invoca il metodo close() sugli
* oggetti usati per le operazioni di I/O, demandandone i medesimi effetti e
* e rilascio di risorse alla invocazione del metodo disconnect() da parte del
* codice utilizzante oggetti della presente classe; ciò permette anche di
* poter compiere più chiamate al metodo handleConnection senza dover ogni
* volta stabilire una nuova connessione.
* handleConnection(String s, String charEnc1, String charEnc2) è un metodo
* che sarà opportuno sovrascrivere in una eventuale estensione della presente
* classe, allo scopo di operare un differente uso della connessione tcp.
*/
public String handleConnection(String s, String charEnc1, String charEnc2) {
//si verifica che una connessione sia stata instaurata.
if (socketConn == null) {
System.out.println("Connessione tcp all'host " + host + " porta " +
String.valueOf(port) + " non disponibile.");
return null;
}
try {
// Si crea un oggetto idoneo all'invio dei dati disponibili in formato
// testuale (appunto ad esempio come un oggetto di tipo String, primo
// argomento del presente metodo), tramite la classe SocketIOUtil
// appositamente definita.
// Tale oggetto gestirà un flusso di output attraverso la connessione tcp
// implementata dalla variabile di classe socketConn.
PrintWriter out = SocketIOUtil.getWriter(socketConn, charEnc1);
// Invio dei dati
out.print(s);
// Ci si assicura che un eventuale buffer in uscita sia svuotato e tutti
// i byte effettivamente inviati a destinazione.
out.flush();
// Un minmo di feedback su
// conferma che l'invio ha
System.out.println("
System.out.println("Numero
quanto inviato, funge anche da
avuto successo.
[stringa inviata]" + s + "[/stringa inviata]");
di caratteri
: " + String.valueOf(
s.length()));
System.out.println("Codifica di carattere : " + charEnc1);
System.out.println("Numero di byte
: " + String.valueOf(s.getBytes(
charEnc1).length));
System.out.println();
// Si crea un oggetto idoneo alla ricezione di informazione di tipo
// testuale tramite la classe SocketIOUtil appositamente
// definita; Tale oggetto gestirà un flusso di output attraverso la
// connessione tcp implementata dalla variabile di classe socketConn.
// I byte in input saranno tradotti in caratteri secondo la
// codifica di carattere corrispondente al nome dato dal valore di
// charEnc2
BufferedReader in = SocketIOUtil.getReader(socketConn, charEnc2);
59
// Ricezione del messaggio di conferma atteso dalla controparte remota.
// Il metodo BufferedReader.read(chat[] cbuff, int offset, int length)
// itera la lettura di caratteri finché non vengono letti e memorizzati
// in cbuff length caratteri,fino ad un EOF o finché la successiva lettura
// non risulterebbe bloccante causa input stream non pronto alla lettura.
// Tale metodo restituisce -1 se lo stream restituisce un EOF (quando
// viene raggiunto il termine del flusso di input) al primo tentativo di
// lettura, altrimenti restituisce il numero di caratteri letti.
// Ovviamente si istanzia il buffer cbuff con una dimensione che si
// possa ragionevolmente supporre maggiore od uguale al numero di char
// che compongono al risposta del server.
// Tutti i caratteri letti vengono memorizzati in una stringa per un
// agevole feedback ed affinché tale stringa venga poi restituita dal
// presente metodo.
char[] cBuff = new char[SERVERMSG_BUFFLENGTH];
int readCharsNum = in.read(cBuff, 0, cBuff.length);
String recievedString = "" + new String(cBuff, 0, readCharsNum);
// Un minimo di feedback su quanto ricevuto, funge anche da
// conferma che la ricezione ha avuto successo.
System.out.println("
[stringa ricevuta]" + recievedString +
"[/stringa ricevuta]");
System.out.println("Numero di caratteri
: " + String.valueOf(
recievedString.length()));
System.out.println("Codifica di carattere : " + charEnc1);
System.out.println("Numero di byte
: " + String.valueOf(
recievedString.getBytes(charEnc2).length));
System.out.println();
return recievedString;
}
//gestione di eventuali eccezioni, con feedback sullo standard output
catch(IOException ioe) {
System.out.println("IOException: " + ioe.getMessage());
ioe.printStackTrace();
return null;
}
catch(Exception e) {
System.out.println("Exception: " + e.getMessage());
e.printStackTrace();
return null;
}
}
}
60
4.5.2 - Classe SocketIOUtil
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché per esempio con "java.io.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre quelle del package
* java.lang, sempre comunque importate implicitamente in automatico).
*/
import java.net.Socket;
import java.io.IOException;
import
import
import
import
java.io.OutputStream;
java.io.InputStream;
java.io.Writer;
java.io.Reader;
import java.io.DataOutputStream;
import java.io.BufferedOutputStream;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.DataInputStream;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.InputStreamReader;
/*
* Una classe "utility" per la creazione dei flussi di input ed
* output; ognuno di tali oggetti invierà o riceverà dati tramite la connessione
* tcp implementata dall'oggetto di tipo Socket che sarà sempre argomento
* dei metodi di questa classe: infatti ogni oggetto implementante un flusso
* di input o di output verrà istanziato come wrapper, diretto o indiretto,
* degli oggetti restituiti dai metodi getOutputStream() e getInputStream()
* invocati sul menzionato oggetto di tipo Socket.
* Classe modificabile contestualmente alla sovrascrittura dei
* metodi "handleConnection" di TCPClient nell'eventuale estensione della classe
* TCPClient.
*/
public class SocketIOUtil {
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Le seguenti costanti di tipo String identificano le codifiche di carattere
che una implementazione dell'ambiente Java standard(sin dalla prima
versione) conforme alle specifiche di Sun deve fornire. Requisito adottato
anche nelle specifiche MHP.
Utili principalmente per essere passate ai metodi della presente classe,
allo scopo di determinare quale codifica di carattere debba essere usata
dagli oggetti dedicati alla gestione dell' I/O quando l'informazione è di
tipo testuale. Si è ritenuto utile definire queste costanti anche perché
le relative codifiche di carattere sono tra le più comuni generalmente
utilizzate.
I valori di queste costanti corrispondono all' "historical name" che le
relative codifiche di carattere hanno nelle specifiche java per quel che
riguarda le stringhe riconosciute dalle classi dei package java.io e
java.lang; tali stringhe rimangono per retrocompatibilità immutate nelle
versioni di java che si succedono nel tempo, e risultano quindi
utilizzabili anche in ambiente MHP (il cui nucleo come noto è basato su un
sottoinsieme delle api vers. 1.1.8).
Gli identificatori delle medesime costanti sono stati scelti uguali al
"canonical name" definito nelle specifiche java per le relative codifiche
di carattere, a meno del carattere '-', non ammissibile in un
identificatore e sostituito con il carattere '_'.
61
* Il canonical name di una codifica di carattere nelle spec. java è usato nel
* package java.io e relativi package inclusi e può variare tra differenti
* versioni delle api in quanto è definito come l'alias che lo IANA
* Charset Registry indica come "preferred MIME name" o, ovviamente, come il
* nome principale riportato da tale documento se esso, il nome, è unico.
* http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
* http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html
* http://www.iana.org/assignments/character-sets
*/
/*
* canonical name: US-ASCII
* descrption
: American Standard Code for Information Interchange
*/
public static final String US_ASCII
= "ASCII";
/*
* canonical name: ISO-8859-1
* descrption
: ISO-8859-1, Latin Alphabet No. 1
*/
public static final String ISO_8859_1 = "ISO8859_1";
/*
* canonical name: UTF-8
* descrption
: Eight-bit Unicode (or UCS) Transformation Format
*/
public static final String UTF_8
= "UTF8";
/*
* canonical name: UTF-16
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
byte order identified by an optional byte-order mark
*/
public static final String UTF_16
= "UTF-16";
/*
* canonical name: UTF-16BE
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
big-endian byte order
*/
public static final String UTF_16BE
= "UnicodeBigUnmarked";
/*
* canonical name: UTF-16LE
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
little-endian byte order
*/
public static final String UTF_16LE
= "UnicodeLittleUnmarked";
/*
* Le seguenti costanti ad uso interno possono essere usate rispettivamente
* con gli opportuni costruttori di BufferedOutputStream/BufferedInputStream e
* BufferedWriter/BufferedReader nel caso si volessero modificare i metodi
* in cui sono impiegati oggetti delle dette classi, al fine di settare
* dimensioni di buffer differenti da quelle predefinite.
*/
/*
* Dimensioni in num. di byte per un buffer di byte.
*/
private static final int byteBufferSize = 512;
/*
* Dimensioni in num. di caratteri per un buffer di char.
*/
private static final int charBufferSize = 512;
62
/*
* Questo metodo fornisce un oggetto di tipo DataOutputStream utile ad inviare
* vari tipi di dato del linguaggio, usando gli opportuni metodi; di
* particolare interesse la possibilità di scrivere nel flusso di output
* direttamente byte ed array di byte, fatto utile
* nel caso non si abbia informazione o non si necessiti di averne riguardo a
* quale debba essere l'interpretazione dei dati da inviare ad un livello
* logico più alto; ad esempio se il componente software utilizzante
* la presente classe debba solo occuparsi, relativamente alla fase di output,
* di invio ad un sistema remoto di dati ottenuti o già in forma binaria.
* L'oggetto di tipo DataOutputStream così creato opera una scrittura
* bufferizzata. Il costruttore utilizzato implica una dimensione del buffer
* pari a quella di default, che nella documentazione ufficiale viene indicata
* come generalmente sufficiente per gli scopi più comuni. Tale dimensione può
* essere settata diversamente tramite l'opportuno costruttore di
* BufferedOutputStream.
* I dati sono effettivamente inviati attraverso il flusso di output al
* dispositivo di destinazione alla chiamata del metodo flush() (l'operazione
* di flush si propaga in cascata a catena attraverso tutti gli eventuali
* flussi di output collegati e i contenuti dei relativi buffer vengono
* inviati a destinazione).
* Ovviamente è innescato un invio effettivo dei dati anche nel caso il buffer
* si riempia.
*/
public static DataOutputStream getBinaryWriter(Socket s) throws IOException {
return new DataOutputStream( (OutputStream) new BufferedOutputStream(
s.getOutputStream()) );
}
/*
* Metodo omologo di getBinaryWriter(Socket s), restituisce anch'esso
* un oggetto di tipo DataOutputStream atto ad assolvere i medesimi compiti
* di quello restituito da getBinaryWriter(Socket s);
* L'oggetto creato e restituito da questo metodo non opera scrittura
* bufferizzata.
*/
public static DataOutputStream getUnbufferedBinaryWriter(Socket s)
throws IOException {
return new DataOutputStream( s.getOutputStream() );
}
/*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
Il seguente metodo fornisce un oggetto di tipo PrintWriter, idoneo nel caso si
abbia a che fare con informazione di tipo testuale contenuta nei comuni
tipi di dato usati in tale caso come char[] o String e si intenda inviarla
eventualmente gestendo quale codifica binaria utilizzare; quando la
codifica di carattere non viene specificata tramite l'opportuno costruttore
viene usata la codifica di carattere che l'ambiente java/mhp ha settata
come predefinita per lo specifico sistema. Il parametro CharEncName è una
stringa che identifica la codifica di carattere secondo la quale si vuole
che siano tradotti in byte i dati di tipo char o String da inviare tramite
gli opportuni metodi di PrintWriter.
Tale oggetto opera comunque una scrittura bufferizzata in quanto
internamente effettua il wrapping dell' output stream passato al
costruttore in un oggetto di tipo OutputStreamWriter che di suo usa un
buffer per i byte da inviare. In questo caso è stato ad esso passato
esplicitamente un oggetto di tipo OutputStreamWriter allo scopo di poter
decidere la codifica di carattere che verrà poi usata dall'oggetto di tipo
PrintWriter restituito da getWriter(Socket s, String CharEncName).
E' stato inoltre inserito un ulteriore layer sottostante di buffering
per i byte in uscita tramite BufferedOutputStream in quanto con l'opportuno
costruttore ciò permette, volendo, di avere un buffer aggiuntivo oltre a
quello di default di OutputStreamWriter, la cui dimensione non è
modificabile dal programmatore.
L'altro layer di buffering dato da BufferedWriter massimizza l'efficienza
del flusso di output in quanto viene tenuto un buffer anche dei caratteri
da codificare in byte, minimizzando le frequenza delle chiamate all'encoder
interno carattere -> sequenza di byte.
63
* I costruttori utilizzati implicano una dimensione dei buffer pari a quella
* di default, che nella documentazione ufficiale viene indicata come
* generalmente sufficiente per gli scopi più comuni. Tale dimensione può
* essere settata diversamente tramite gli opportuni costruttori.
* Il Secondo parametro passato al costruttore di PrintWriter, se "true"
* attiva l'autoflush, ovvero ad ogni operazione
* di scrittura tramite l'opportuno metodo di PrintWriter (ovvero in tre
* casi, quando sono chiamati i metodi println, printf, o format), i dati
* vengono effettivamente inviati tramite il flusso di output al dispositivo
* di destinazione. Settato a false disattiva l'autoflush: ad ogni operazione
* di scrittura tramite l'opportuno metodo di PrintWriter, i dati vengono
* conservati in un buffer di uscita per essere
* effettivamente inviati attraverso il flusso di output al dispositivo di
* destinazione alla chiamata del metodo flush (l'operazione di flush si
* propaga in cascata a catena attraverso tutti gli eventuali flussi di output
* collegati e i contenuti dei relativi buffer vengono inviati a
* destinazione).
* Ovviamente è innescato un invio effettivo dei dati anche nel caso il buffer
* dovesse riempirsi.
*/
public static PrintWriter getWriter(Socket s, String CharEncName)
throws IOException {
BufferedOutputStream bOS = new BufferedOutputStream(s.getOutputStream());
OutputStreamWriter oSW = new OutputStreamWriter((OutputStream) bOS,
CharEncName);
BufferedWriter bW = new BufferedWriter((Writer) oSW);
return new PrintWriter((Writer) bW, false);
}
/*
* Metodo wrapper utile ad ottenere un oggetto di tipo PrintWriter così come
* creato da getWriter(Socket s, String CharEncName), ma che usi la
* codifica di carattere di default del sistema.
*/
public static PrintWriter getWriter(Socket s) throws IOException {
return getWriter(s, new OutputStreamWriter(
(OutputStream)System.out).getEncoding());
}
/*
* Il metodo che segue è del tutto analogo come finalità a
* getWriter(Socket s, String CharEncName), la differenza sostanziale risiede
* nel fatto che l'oggetto restituito per la gestione del flusso di output non
* implementa alcun tipo di buffering; ciò può rappresentare una scelta utile
* qualora sia un obbiettivo primario minimizzare l'impronta del codice in
* memoria e/o in generale siano trascurabili i benefici di uno stack di
* output più complesso ma bufferizzato, nel caso per esempio le operazioni
* di scrittura/invio da effettuare siano in numero molto ridotto.
* Con l'oggetto di tipo PrintStream non è però possibile scegliere una
* codifica di carattere diversa da quella che l'ambiente java/mhp ha settata
* come predefinita per lo specifico sistema, in quanto un costruttore che
* permetta tale operazione, direttamente, o indirettamente, attraverso i
* parametri da passare ad esso, non è incluso nel sottoinsieme delle api java
* standard che costituiscono il nucleo del middleware mhp.
* Pertanto nel caso si necessiti di una codifica di carattere diversa da
* quella di default e contemporaneamente di non avere alcun buffer in output
* conviene tradurre preventivamente l'informazione in un array di
* byte da inviare poi tramite l'opportuno metodo di PrintStream o anche
* sfruttando altre classi più specificamente adatte come ad esempio,
* DataOutputStream.
*/
public static PrintStream getUnbufferedWriter(Socket s) throws IOException {
return new PrintStream(s.getOutputStream());
}
64
/* Questo metodo fornisce un oggetto di tipo DataInputStream utile a leggere
* i dati in input interpretandoli secondo vari tipi di dato del linguaggio,
* con gli opportuni metodi; di particolare interesse la possibilità di
* leggere dal flusso di input direttamente byte ed array di byte, fatto utile
* nel caso non si abbia informazione o non si necessiti di averne riguardo a
* quale debba essere l'interpretazione dei dati ricevuti ad un livello logico
* più alto; ad esempio se il componente software utilizzante la presente
* classe debba solo occuparsi, relativamente alla fase di input, della
* memorizzazione persistente di quanto letto
* così com'è.
* L'oggetto di tipo DataInputStream così creato opera una lettura
* bufferizzata, acquisendo ad ogni operazione di lettura tramite i relativi
* metodi una quantità di dati maggiore di quanto strettamente richiesto e
* creando un buffer interno, in modo da ridurre i necessari accessi
* effettivi al dispositivo di input. Il costruttore utilizzato implica una
* dimensione del buffer pari a quella di default, che nella documentazione
* ufficiale viene indicata come generalmente sufficiente per gli scopi più
* comuni. Tale dimensione può essere settata diversamente tramite l'opportuno
* costruttore di BufferedInputStream.
*/
public static DataInputStream getBinaryReader(Socket s) throws IOException {
return new DataInputStream( (InputStream) new BufferedInputStream(
s.getInputStream()) );
}
/*
* Metodo omologo di getBinaryReader(Socket s), restituisce anch'esso
* un oggetto di tipo DataInputStream atto ad assolvere i medesimi compiti
* di quello restituito da getBinaryReader(Socket s);
* L'oggetto creato e restituito da questo metodo non opera lettura
* bufferizzata.
*/
public static DataInputStream getUnbufferedBinaryReader(Socket s)
throws IOException {
return new DataInputStream( s.getInputStream() );
}
/*
* Questo metodo fornisce un oggetto per la ricezione di dati idoneo in caso
* l'informazione sia di tipo testuale e la si voglia manipolare con i
* relativi tipi di dato direttamente in fase di lettura del flusso di input.
* Come da nome del tipo di oggetto, BufferedReader, verrà operata una lettura
* bufferizzata, acquisendo ad ogni operazione di lettura tramite i relativi
* metodi una quantità di dati maggiore di quanto strettamente richiesto e
* creando un buffer interno, in modo da ridurre i necessari accessi
* effettivi al dispositivo di input. Il costruttore utilizzato implica una
* dimensione del buffer pari a quella di default, che nella documentazione
* ufficiale viene indicata come generalmente sufficiente per gli scopi più
* comuni. tale dimensione può essere settata diversamente tramite l'opportuno
* costruttore di BufferedReader.
* Il parametro CharEncName è una stringa che identifica la codifica di
* carattere secondo la quale si vuole che siano interpretati i byte ricevuti
* affinché gli opportuni metodi di BufferedReader restituiscano dati di tipo
* char o String il cui contenuto sia appunto coerente con la codifica di
* carattere scelta.
*/
public static BufferedReader getReader(Socket s, String charEncName)
throws IOException {
return new BufferedReader( (Reader) new InputStreamReader(
s.getInputStream(), charEncName) );
}
/*
* Metodo wrapper utile ad ottenere un oggetto di tipo BufferedReader così
* come creato da getReader(Socket s, String charEncName), ma che usi la
* codifica di carattere di default del sistema.
*/
public static BufferedReader getReader(Socket s) throws IOException {
return getReader(s, new InputStreamReader(System.in).getEncoding());
}
65
/*
* Metodo omologo di getReader(Socket s, String charEncName) che restituisce
* un oggetto di tipo InputStreamReader atto ad assolvere ai compiti di
* lettura di un flusso di input costituito da informazione testuale;
* l'oggetto creato e restituito da questo metodo non opera lettura
* bufferizzata.
*/
public static InputStreamReader getUnbufferedReader(Socket s,
String charEncName) throws IOException {
return new InputStreamReader(s.getInputStream(), charEncName);
}
/*
* Metodo wrapper utile ad ottenere un oggetto di tipo InputStreamReader così
* come creato da getUnbufferedReader(Socket s, String charEncName), ma che
* usi la codifica di carattere di default del sistema.
*/
public static InputStreamReader getUnbufferedReader(Socket s)
throws IOException {
return getUnbufferedReader(s, new InputStreamReader(
System.in).getEncoding());
}
}
4.6 - Un server per la ricezione dei dati inviati
Si propone inoltre, oltre al codice indirizzato all’ambiente MHP, un semplice
esempio per un componente software, che in esecuzione sul sistema remoto di
destinazione per i dati acquisiti, riceva i medesimi appunto tramite connessione
tcp/ip. Esso è stato realizzato con codice Java pensato per l’esecuzione in un
comune ambiente di esecuzione JSE (Java Standard Edition), dato che nel
contesto discusso e riguardo alle operazioni prese in esame, ben difficilmente il
sistema di destinazione sarà un altro terminale MHP o un sistema impossibilitato
a disporre di un ambiente Java standard. La seguente specifica implementazione
risulta tuttavia idonea all’esecuzione anche in ambiente MHP, una volta incluso
il codice in una opportuna Xlet.
4.6.1 - Classe TCPserver
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché per esempio con "java.io.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre a quelle dell'
* eventuale package di appartenenza e quelle del package java.lang, sempre
* comunque importate implicitamente in automatico).
*/
import java.net.ServerSocket;
import java.net.Socket;
66
import
import
import
import
java.io.DataInputStream;
java.io.BufferedReader;
java.io.PrintWriter;
java.io.IOException;
/*
* Questa classe implementa un server di rete secondo
* un modello basato sull'utilizzo delle classi Socket e ServerSocket
* che permette accettare la connessione chiesta da un client e di scambiare
* dati con esso.
* Presenta un'impostazione di validità abbastanza generale tale che,
* in una reimplementazione che svolga compiti specifici differenti, la nuova
* classe possa essere sottoclasse di questa sovrascrivendo con modifiche
* sostanziali i soli metodi "hadleConnection".
* Si usa SocketUtil come interfaccia per la creazione degli oggetti che
* forniscono i flussi di I/O ( PrintWriter and BufferedReader in questo caso ),
* in modo da semplificare il relativo codice e rendere l'insieme
* sufficientemente modulare.
*/
public class TCPServer {
/*
* num. di byte >= a quello effettivo dei dati da ricevere
*/
protected static final int BYTEDATA_BUFFLENGTH = 1000;
/*
* num. di char >= a quello effettivo dei dati da ricevere, se questi sono
* da interpretare come sequenza di caratteri
* (un code-point unicode, qualunque sia la codifica binaria tra quelle
* comunemente usate, richiede al massimo 4 byte; ciò peraltro nel caso
* di caratteri di raro utilizzo, appartenenti ad alfabeti non latini)
*/
protected static final int CHARDATA_BUFFLENGTH = 4*BYTEDATA_BUFFLENGTH;
/*
* Costante che contiene il nome della codifica di carattere
* da utilizzare se l'informazione da ricevere è di tipo testuale e si vuole
* gestire la lettura del flusso di input direttamente come
* stringa o sequenza di caratteri.
* Da passare volendo come secondo argomento al metodo
* handleConnection(Socket server, String charEnc1, String charEnc2).
*/
protected static final String DATA_CHARENC = SocketIOUtil.UTF_8;
/*
* Costante che contiene il nome della codifica di carattere da utilizzare
* nell'invio del messaggio di avvenuta ricezione dei dati inviati da un
* client.
* Da passare volendo come secondo argomento al metodo
* handleConnection(Socket server, String charEnc) o come terzo all'omologo
* con tre argomenti.
*/
protected static final String MSG_CHARENC = SocketIOUtil.UTF_8;
/*
* Stringa corrispondente al messaggio che si invia a conferma della ricezione
* dati.
*/
protected static final String DATARECIEVED_MSG = "Dati ricevuti.";
/*
* Num. della porta su cui si attendono connessioni tcp.
*/
protected int port, maxConnections;
67
/*
* Crea un server che sarà in ascolto alla porta "port" specificata
* e accetterà fino ad un massimo di "maxConnecitions" ognuna
* gestita tramite una chiamata a handleConnection();
*/
public TCPServer(int port, int maxConnections) {
setPort(port);
setMaxConnections(maxConnections);
}
/*
* Controlla la porta per la connessione. Ogni nuova connessione
* stabilita, passa il relativo oggetto Socket ad "handleConnection".
* il server rimarrà in ascolto fino all'invio di un comando esplicito
* di uscita (ad esempio "System.exit(int exitCode)") o fino al raggiungimento
* del limite per il numero di connessioni accettabili; con "maxConnecitions"
* uguale a 0 rimarrà in esecuzione all'infinito.
*/
public void listen() {
int i=0;
try {
ServerSocket listener = new ServerSocket(port);
Socket server;
System.out.println("Server in ascolto sulla porta " + String.valueOf(port)
+ ". Numero massimo di connesioni gestibili: " +
String.valueOf(maxConnections));
System.out.println();
while((i < maxConnections) || (maxConnections == 0)) {
server = listener.accept();
i++;
System.out.println("Accettata connessione tcp " + String.valueOf(i) +
" di " + String.valueOf(maxConnections) + ", da " +
server.getInetAddress().getHostName());
System.out.println();
// Gestione della connessione: ricezioni dati leggendo lo stream di
// come sequenza di byte; invio messaggio di conferma convertito in byte
// secondo la codifica di carattere MSG_CHARENC.
handleConnection(server, MSG_CHARENC);
//
//
//
//
//
Gestione della connessione: ricezioni dati leggendo lo stream di
come sequenza di caratteri, in base alla codifica di carattere
DATA_CHARENC; invio messaggio di conferma convertito in byte
secondo la codifica di carattere MSG_CHARENC.
handleConnection(server, DATA_CHARENC, MSG_CHARENC);
// Chiusura della singola connessione ed I/O streams associati.
server.close();
System.out.println("Connessione tcp con " +
server.getInetAddress().getHostName() + " chiusa.");
System.out.println();
}
System.out.println("Gestite " + String.valueOf(i) +
" connessioni, pari al numero massimo prestabilito." +
" Esecuzione terminata.");
}
catch (IOException ioe) {
System.out.println("IOException: " + ioe);
ioe.printStackTrace();
}
}
68
protected void handleConnection(Socket server, String charEnc)
throws IOException {
// Si crea un oggetto idoneo alla ricezione di informazione di tipo
// testuale tramite la classe SocketIOUtil appositamente
// definita; Tale oggetto gestirà un flusso di output attraverso la
// connessione tcp implementata dalla variabile di classe socketConn.
// I byte in input saranno tradotti in caratteri secondo la
// codifica di carattere corrispondente al nome dato dal valore di
// charEnc1
DataInputStream in = SocketIOUtil.getBinaryReader(server);
// Ricezione dei dati come sequenza di byte,
// Il metodo DataInputStream.read(byte[] bBuff)
// itera la lettura di byte finché non viene riempito bBuff o fino ad un
// EOF o finché la successiva lettura non risulterebbe bloccante causa
// input stream non pronto alla lettura.
// Tale metodo restituisce -1 se lo stream restituisce un EOF (quando
// viene raggiunto il termine del flusso di input) al primo tentativo di
// lettura, altrimenti restituisce il numero di byte letti.
// Ovviamente si istanzia il buffer bBuff con una dimensione che si
// possa ragionevolmente supporre maggiore od uguale al numero di byte
// che compongono i dati in input.
// Si copiano poi i byte contenuti in bBuff in un byte[] di dimensione pari
// al numero di byte ricevuti.
byte[] bBuff = new byte[BYTEDATA_BUFFLENGTH];
int readCharsNum = in.read(bBuff);
byte[] data = new byte[readCharsNum];
for (int i=0; i<data.length; i++) {
data[i] = bBuff[i];
}
// Un minimo di feedback su quanto ricevuto, funge anche da
// conferma che la ricezione ha avuto successo.
String sentBytes = "";
for (int i=0; i<data.length; i++) {
sentBytes = sentBytes + new Byte(data[i]).toString() + " ";
}
System.out.println("Numero di byte ricevuti
: " +
String.valueOf(data.length));
System.out.println("Byte ricevuti (corrispondenti valori decimali) : "
+ sentBytes);
System.out.println();
// Tipicamente a questo punto si dovrà farne qualcosa dei dati ricevuti,
// come ad esempio inoltrarli ad altri componenti software o provvedere
// ad una qualche forma di memorizzazione persistente.
// Si crea un oggetto idoneo all'invio dei dati disponibili in formato
// testuale (appunto ad esempio come oggetto di tipo String, primo
// argomento del presente metodo), tramite la classe SocketIOUtil
// appositamente definita.
// Tale oggetto gestirà un flusso di output attraverso la connessione tcp
// implementata dalla variabile di classe socketConn.
PrintWriter out = SocketIOUtil.getWriter(server, charEnc);
// Invio dei dati
out.print(DATARECIEVED_MSG);
// Ci si assicura che un eventuale buffer in uscita sia svuotato e tutti
// i byte effettivamente inviati a destinazione.
out.flush();
69
// Un minimo di feedback su quanto inviato, funge anche da
// conferma che l'invio ha avuto successo.
System.out.println("
[stringa inviata]" + DATARECIEVED_MSG +
"[/stringa inviata]");
System.out.println("Numero di caratteri
: " +
String.valueOf(DATARECIEVED_MSG.length()));
System.out.println("Codifica di carattere : " + charEnc);
System.out.println("Numero di byte
: " + String.valueOf(
DATARECIEVED_MSG.getBytes(charEnc).length));
System.out.println();
}
protected void handleConnection(Socket server, String charEnc1,
String charEnc2) throws IOException{
// Si crea un oggetto idoneo alla ricezione di informazione di tipo
// testuale tramite la classe SocketIOUtil appositamente
// definita; Tale oggetto gestirà un flusso di output attraverso la
// connessione tcp implementata dalla variabile di classe socketConn.
// I byte in input saranno tradotti in caratteri secondo la
// codifica di carattere corrispondente al nome dato dal valore di
// charEnc1
BufferedReader in = SocketIOUtil.getReader(server, charEnc1);
// Ricezione del messaggio di conferma atteso dalla controparte remota.
// Il metodo BufferedReader.read(chat[] cbuff, int offset, int length)
// itera la lettura di caratteri finché non vengono letti (e memorizzati in
// cbuff)length caratteri, fino ad un EOF o finché la successiva lettura
// non risulterebbe bloccante causa input stream non pronto alla lettura.
// Tale metodo restituisce -1 se lo stream restituisce un EOF (quando
// viene raggiunto il termine del flusso di input) al primo tentativo di
// lettura, altrimenti restituisce il numero di caratteri letti.
// Ovviamente si istanzia il buffer cbuff con una dimensione che si
// possa ragionevolmente supporre maggiore od uguale al numero di char
// che compongono al risposta del server.
// Tutti i caratteri letti vengono memorizzati in una stringa per un
// agevole feedback ed affinché tale stringa venga poi restituita dal
// presente metodo.
char[] cBuff = new char[CHARDATA_BUFFLENGTH];
int readCharsNum = in.read(cBuff, 0, cBuff.length);
String recievedString = "" + new String(cBuff, 0, readCharsNum);
// Un minimo di feedback su quanto ricevuto, funge anche da
// conferma che la ricezione ha avuto successo.
System.out.println("
[stringa ricevuta]" + recievedString +
"[/stringa ricevuta]");
System.out.println("Numero di caratteri
: " + String.valueOf(
recievedString.length()));
System.out.println("Codifica di carattere : " + charEnc1);
System.out.println("Numero di byte
: " + String.valueOf(
recievedString.getBytes(charEnc1).length));
System.out.println();
// tipicamente a questo punto si dovrà farne qualcosa dei dati ricevuti,
// come ad esempio inoltrarli ad altri componenti software o provvedere
// ad una qualche forma di memorizzazione persistente.
// Si crea un oggetto idoneo all'invio dei dati disponibili in formato
// testuale (appunto ad esempio come un oggetto di tipo String, primo
// argomento del presente metodo), tramite la classe SocketIOUtil
// appositamente definita.
// Tale oggetto gestirà un flusso di output attraverso la connessione tcp
// implementata dalla variabile di classe socketConn.
PrintWriter out = SocketIOUtil.getWriter(server, charEnc2);
// Invio dei dati
out.print(DATARECIEVED_MSG);
// Ci si assicura che un eventuale buffer in uscita sia svuotato e tutti
// i byte effettivamente inviati a destinazione.
out.flush();
70
// Un minimo di feedback su quanto inviato, funge anche da
// conferma che l'invio ha avuto successo.
System.out.println("
[stringa inviata]" + DATARECIEVED_MSG +
"[/stringa inviata]");
System.out.println("Numero di caratteri
: " + String.valueOf(
DATARECIEVED_MSG.length()));
System.out.println("Codifica di carattere : " + charEnc2);
System.out.println("Numero di byte
: " + String.valueOf(
DATARECIEVED_MSG.getBytes(charEnc2).length));
System.out.println();
}
/*
* tipico metodo getter per la variabile di istanza "maxConnections"
*/
public int getMaxConnections() {
return(maxConnections);
}
/*
* tipico metodo setter per la variabile di istanza "maxConnections"
*/
public void setMaxConnections(int maxConnections) {
this.maxConnections = maxConnections;
}
/*
* tipico metodo getter per la variabile di istanza "port"
*/
public int getPort() {
return(port);
}
/*
* tipico metodo setter per la variabile di istanza "port"
*/
protected void setPort(int port) {
this.port = port;
}
}
4.6.2 - Classe TCPServerTest
/*
* Applicazione java che provvede ad istanziare e mettere in attesa di
* connessioni un oggetto di tipo TCPServer; la porta su cui accettare
* conenssioni e il numero massimo per queste vengono letti come parametri dalla
* riga di comando; in caso uno dei due o entrambi non vengano forniti,
* saranno usati dei valori predefiniti.
*/
public class TCPServerTest {
private static final int DEFAULT_PORT = 8088;
private static final int DEFAULT_MAXCONNECTNUM = 2;
public static void main(String[] args) {
int port = DEFAULT_PORT;
int maxConnectNum = DEFAULT_MAXCONNECTNUM;
if (args.length >= 2) {
port = Integer.parseInt(args[0]);
maxConnectNum = Integer.parseInt(args[1]);
}
if (args.length == 1) {
port = Integer.parseInt(args[0]);
}
TCPServer s = new TCPServer(port, DEFAULT_MAXCONNECTNUM);
s.listen();
}
}
71
4.6.3 - Classe SocketIOUtil in TCPServer
Il codice per tale classe usata in TCPServer è il medesimo esposto nel paragrafo
4.5.2.
4.7 - Accedere ad Una Risorsa nell’Object Carousel
Accedere ad un qualche tipo di risorsa presente nel broadcast filesystem
implementato con il sistema del DSMCC Object Carousel è una azione piuttosto
comune da parte di una applicazione MHP.
Sebbene per gli obiettivi specifici esposti nel presente documento non sia
essenziale, si ritiene possa tornare utile, per una Xlet che usi oggetti istanza delle
classi fin qui definite, definire una ulteriore classe che permetta di leggere da un
file di configurazione, che si suppone presente nell’object carousel di interesse
insieme ai file di bytecode della applicazione, parametri dai quali il codice di
una applicazione sia indipendente, potendo così quando necessario modificare
un singolo file tra quelli da trasmettere nell’object carousel ed evitando
operazioni di ricompilazione.
4.7.1 - Un File di Configurazione
Segue un esempio di file di file di configurazione con sintassi XML e dalla
struttura specifica elementare, che integra alcuni parametri usati nelle classi
esposte nei precedenti paragrafi:
<?xml version="1.0" encoding="UTF-8"?>
<RS232toTCP_Config>
<DataAcqFileName>AcquiredData.txt</DataAcqFileName>
<ISPLoginParameters>
<PhoneNum>0000991199</PhoneNum>
<UserName>UserName</UserName>
<Password>Password</Password>
</ISPLoginParameters>
<TCPConnectionParameters>
<TCPTargetHost>127.0.0.1</TCPTargetHost>
<TCPTargetPort>8088</TCPTargetPort>
<TCPConnectionParameters>
</RS232toTCP_Config>
(Naturalmente i valori mostrati sono arbitrari e non corrispondono ad alcuna
informazione reale).
72
4.7.2 - Classe ConfigFileParser
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché ad esempio con "javax.tv.xlet.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre a quelle dell'
* eventuale package di appartenenza e quelle del package java.lang, sempre
* comunque importate implicitamente in automatico).
*/
import javax.tv.xlet.XletContext;
import javax.tv.service.selection.ServiceContextFactory;
import org.davic.net.Locator;
import org.dvb.dsmcc.ServiceDomain;
import org.dvb.dsmcc.DSMCCObject;
//import java.io.File;
import java.io.FileInputStream;
public class ConfigFileParser {
/*
* Le seguenti costanti di tipo String identificano le codfifiche di carattere
* che una implementazione dell'abiente Java standard(sin dalla prima
* versione) conforme alle specifiche di Sun deve fornire. Requisito adottato
* anche nelle specifiche MHP.
* Utili in questo caso per essere passate al metodo "load" della presente
* classe d aparte del codice chiamante, in modo da convertire i byte del
* file di configurazione in sequeza di caratteri secondo la opportuna
* codifica di carattere.
* Si è ritenuto utile definire queste costanti anche perché
* le relative codifiche di carattere sono tra le più comuni generalmente
* utilizzate.
* I valori di queste costanti corrispondono all' "historical name" che le
* relative codifiche di carattere hanno nelle specifiche java per quel che
* riguarda le stringhe riconosciute dalle classi dei package java.io e
* java.lang; tali stringhe rimangono per retrocompatibilità immutate nelle
* versioni di java che si succedono nel tempo, e risultano quindi
* utilizzabili anche in ambiente MHP (il cui nucleo come noto è basato su un
* sottoinsieme delle api vers. 1.1.8).
* Gli identificatori delle medesime costanti sono stati scelti uguali al
* "canonical name" definito nelle specifiche java per le relative codifiche
* di carattere, a meno del carattere '-', non ammissibile in un
* identificatore e sostituito con il carattere '_'.
* Il canonical name di una codifica di carattere nelle spec. java è usato nel
* package java.io e relativi package inclusi e può variare tra differenti
* versioni delle API in quanto è definito come l'alias che lo IANA
* Charset Registry indica come "preferred MIME name" o, ovviamente, come il
* nome principale riportato da tale documento se esso, il nome, è unico.
* http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
* http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html
* http://www.iana.org/assignments/character-sets
*/
/*
* canonical name: US-ASCII
* descrption
: American Standard Code for Information Interchange
*/
public static final String US_ASCII
= "ASCII";
/*
* canonical name: ISO-8859-1
* descrption
: ISO-8859-1, Latin Alphabet No. 1
*/
public static final String ISO_8859_1 = "ISO8859_1";
/*
* canonical name: UTF-8
* descrption
: Eight-bit Unicode (or UCS) Transformation Format
*/
public static final String UTF_8
= "UTF8";
73
/*
* canonical name: UTF-16
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
byte order identified by an optional byte-order mark
*/
public static final String UTF_16
= "UTF-16";
/*
* canonical name: UTF-16BE
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
big-endian byte order
*/
public static final String UTF_16BE
= "UnicodeBigUnmarked";
/*
* canonical name: UTF-16LE
* descrption
: Sixteen-bit Unicode (or UCS) Transformation Format,
*
little-endian byte order
*/
public static final String UTF_16LE
= "UnicodeLittleUnmarked";
/*
* Variabile che identifica l'object carousel di interesse in caso
* il dvb service a cui è associata la xlet ne contenga più di uno.
* Quando vi è un solo carousel il valore utilizzato è solitamente "1".
*/
private final int CAROUSEL_ID= 1;
/*
* Dimensione di un buffer in cui memorizzare il contenuto del file di
* configurazione prima di convertirlo nella stringa corrispondente secondo
* la codifica di carattere scelta.
* Valore usato come dimensione di un byte[], va scelto un valore
* ragionevolmente >= del num. di byte del file di configurazione, senza essere
* critico quanto ad occupazione di memoria.
*/
private final int BUFFER_DIM = 40000;
/*
* Stringa adibita a rappresentare il contenuto del file di configurazione.
*/
private String configFileContent;
/*
* Array di strighe corrispondenti ai parametri che si intendono ricavare dal
* parsing del contenuto del file di configurazione.
*/
private String[] values;
public ConfigFileParser(){
}
74
/*
* Il seguente metodo accede, a partire dal contesto di esecuzione della xlet
* rappresentato dall'oggetto xc, al broadcast filesystem implementato dall'
* object carousel presente nel dvb service a cui è associata la xlet, legge
* il contenuto del file corrispondente al path relativo configFilePath
* e lo memorizza interamente nella apposita variabile di classe,
* convertendo la sequenza di byte in caratteri secondo la codifica di
* carattere charEnc.
* Restituisce false se si verifica una eccezione o se il file cercato non
* esiste nell' object carousel
*/
public boolean load(XletContext xc, String configFilePath, String charEnc) {
try {
// S ottiene un oggetto di tipo locator che identifica il dvb service
// contenente l'object carousel di interesse; in questo caso lo stesso
// service in cui è trasmessa la xlet il cui contesto è rappresentato
// dall'oggetto xc di tipo XletContext.
Locator locator = (org.davic.net.Locator)ServiceContextFactory.getInstance(
).getServiceContext(xc).getService().getLocator();
// Si crea un oggetto di tipo ServiceDomain che implementa una
// rappresentazione di object carousel ad uso delle applicazioni mhp;
// lo si collega poi al carousel di interesse trasmesso nel service
// identificato da locator.
ServiceDomain carousel = new ServiceDomain();
carousel.attach(locator, this.CAROUSEL_ID);
// Se l'oggetto di tipo Locator punta direttamente ad un elemtary stream
// conetenente il carousel o se il relativo dvb service ne contiene uno
// solo si può anche usare la variante:
// carousel.attach(locator);
// Si ottiene il path per il mount point del filesystem "virtuale"
// con cui il middleware espone alle applicazioni i contenuti
// dell'object carousel. la classe DSMCCObject estende java.io.File
// e permette un uso analogo dei relativi oggetti. naturalmente si ha
// a che fare con un filesystem in sola lettura.
DSMCCObject basePath = carousel.getMountPoint();
// Si crea l'oggetto rappresentativo del file che abbia path
// configFilePath relativo al path del punto di mount del carousel sopra
// selezionato e "montato".
DSMCCObject cF = new DSMCCObject(basePath, configFilePath);
//File cF = new File(configFilePath);
// Se il file cercato non è presente nell' object carousel, si termina
// restituendo false
if (cF.exists() == false ) {
return false;
}
// Si legge il contenuto del file esattamente come se fosse su
// un filesystem di tipo tradizionale
FileInputStream fIS = new FileInputStream(cF);
byte[] buff = new byte[BUFFER_DIM];
int readBytesNum = fIS.read(buff);
fIS.close();
// Si notifica al middleware mhp che non si ha più bisogno
// di acccedere al carousel e che le relative risorse possono essere
// liberate
carousel.detach();
75
// Segue quanto necessario per covertire la sequenza di byte letta in una
// stringa, secondo la codifica di carattere charEnc
byte[] content = new byte[readBytesNum];
for (int i=0; i<content.length; i++) {
content[i] = buff[i];
}
this.configFileContent = new String(content, charEnc);
this.values = null;
return true;
}
catch(Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
return false;
}
}
/*
* Metodo che implementa un parsing elementare funzionale per un file di
* configurazione con la struttura mostrata nel precedente paragrafo.
* Non si implementa un parsing di utilità generale per un documento xml
* in quanto esula dagli scopi del presente elaborato.
* parse() andrà sovrascritto in una eventuale estensione della presente classe
* atta ad usare file di configurazione diversamente strutturati
*/
public String[] parse() {
if (this.configFileContent == null) {
return null;
}
if (this.values != null) {
return this.values;
}
String tagOpen = "<";
String tagClosed = ">";
String elementClosed = "/";
String[] elemNames = {"DataAcqFileName", "PhoneNum", "UserName", "Password",
"TCPTargetHost", "TCPTargetPort"};
String[] values = new String[elemNames.length];
for (int i=0; i<values.length; i++) {
values[i] = getElementContent(tagOpen+elemNames[i]+tagClosed,
tagOpen+elementClosed+elemNames[i]+tagClosed);
if (values[i] == null) {
return null;
}
}
this.values = values;
return values;
}
/*
* Metodo di utilità interna usato da parse();
*/
protected String getElementContent(String openingTag, String closureTag) {
int openingTagIndex = this.configFileContent.indexOf(openingTag);
if (openingTagIndex == -1) {
return null;
}
int contentStartIndex = openingTagIndex + openingTag.length();
int closureTagIndex = this.configFileContent.indexOf(closureTag,
contentStartIndex);
if (closureTagIndex == -1) {
return null;
}
return this.configFileContent.substring(contentStartIndex, closureTagIndex);
}
}
76
4.8 - Una Xlet minimale
Si mostra di seguito il codice per una semplice Xlet, atta principalmente a
permettere l’esecuzione e quindi il testing del codice precedentemente esposto
in un ambiente di esecuzione MHP, ovvero idealmente un STB conforme alla
specifica o anche un emulatore, che tipicamente può esssere eseguito su
qualunque sistema permetta un ambiente Java standard; emulatore che
implementi almeno l’emulazione di tutte le classi dell’api MHP effettivamente
usate. Ad esempio emulatori liberamente disponibili con licenza, quale la GNU
Public License, che permetta volendo di averne il codice sorgente e di
modificarlo qualora lo si ritenesse opportuno, sono “XletView” ed “OpenMHP”.
4.8.1 - Classe RS232toTCP
/*
* Come prassi, si importano tutte le classi necessarie.
* Viene fatto esplicitamente anziché ad esempio con "javax.tv.xlet.*" affinché
* il codice della classe che si va a definire sia preceduto da una lista di
* tutte le classi di libreria in essa utilizzate (oltre a quelle dell'
* eventuale package di appartenenza e quelle del package java.lang, sempre
* comunque importate implicitamente in automatico).
*/
import javax.tv.xlet.Xlet;
import javax.tv.xlet.XletContext;
import javax.tv.xlet.XletStateChangeException;
/*
* Questa classe implementa tutti i metodi definiti dall'
* interfaccia javax.tv.xlet.Xlet, in modo da poter essere la classe
* principale di una applicazione MHP o appunto "xlet" che dir si voglia.
*/
public class RS232toTCP implements Xlet, Runnable {
private final String SYSPROPNAME_SEPARATOR = "file.separator";
private final String XLETPROPNAME_ORGID = "dvb.org.id";
private final String XLETPROPNAME_APPID = "dvb.app.id";
private
private
private
private
final
final
final
final
int
int
int
int
STATE_LOADED = 0;
STATE_PAUSED = 1;
STATE_ACTIVE = 2;
STATE_DESTROYED = 3;
// Path relativo per un file di configurazione presente nell'object carousel;
// relativo al path del punto di mount dell' object carousel.
private final String CONFIGFILEPATH = "RS232toTCP//config.xml";
private int state = STATE_LOADED;
/*
* Oggetto della classe rappresentativa del contesto di esecuzione di una Xlet.
* Ogni applicazione MHP ottiene in fase di inizializzazione, tramite
* initXlet(XletContext context), un oggetto di questo tipo dall'ambiente di
* esecuzione.
* Tale variabile di istanza ha lo scopo di mantenere un riferimento a tale
* oggetto finché la xlet esiste e che sia accessibile da tutti i metodi della
* xlet stessa.
*/
private XletContext context;
77
private boolean started = false;
private Thread thread;
/*
* Ogni xlet dovrebbe avere un costruttore privo di argomenti;
* Un tale costruttore è l'unico invocato dall'ambiente di esecuzione
* nell'attivare una xlet.
*/
public RS232toTCP() {
// Le raccomandazioni della specifica mhp prediligono comunque la scelta che
// questo costruttire non faccia nulla, lasciando ogni inizializzazione
// al metodo initXlet; o a quello startXlet per quel che riguarda operazioni
// di inizializzazione che siano resource-intensive.
//
}
/*
* Metodo destinato alle operazioni di inizializzazione; riceve dall'ambiente
* di esecuzione un oggetto di tipo XletContex rappresentativo del contesto
* di esecuzione della xlet (e che permette di interagire con esso); è buona
* norma mantenere un riferimento a tale oggetto, dato che comunemente esso
* torna utile.
* La definizione dell'interfaccia xlet raccomanda che azioni di
* inizializzazione che facciano uso intensivo di risorse siano demandate al
* metodo startXlet.
*/
public void initXlet(XletContext context) throws XletStateChangeException {
if (this.state != this.STATE_LOADED) {
throw new XletStateChangeException("Xlet già inizializzata");
}
this.state = this.STATE_PAUSED;
this.context = context;
}
/*
* Metodo a cui è affidato l'avvio dell'esecuzione effettiva dei compiti
* per cui la xlet è stata creata; con questo metodo si suppone la xlet
* si mostri sullo schermo ed inizi l'interazione con l'utente.
* Analogamente a quanto raccomandato per initXlet, startXlet dovrebbe
* lanciare una XletStateChangeException in caso di condizioni di errore,
* notificando all'ambiente mhp che l'avvio della xlet è fallito.
* La definizione della semantica di questo metodo nelle specifiche impone che
* esso termini e l'esecuzione ritorni al chiamante; è quindi considerata
* la prassi più adeguata che questo metodo si limiti sostanzialmente
* ad avviare l'effettiva esecuzione dei compiti della xlet in un nuovo thread,
* motivo per cui la presente classe implementa anche l'interfaccia Runnable,
* in modo che la xlet possa svolgere il lavoro effettivo eseguendo il metodo
* run() in un nuovo thread diverso da quello di startXlet.
*/
public void startXlet() throws XletStateChangeException {
if (this.state != this.STATE_PAUSED) {
throw new XletStateChangeException("La Xlet deve essere nello stato " +
"\"PAUSED\" per potre essere avviata");
}
// avvio in un nuovo thread coem da "best-practice", sebbene in questo caso
// non strettamente necessario in quanto il metodo run() esegue una sequenza
// di operazioni e ritorna.
if (started == false) {
this.started = true;
this.thread = new Thread(this);
this.thread.start();
}
else {
this.thread.resume();
}
this.state = this.STATE_ACTIVE;
}
78
/*
* Metodo adibito a porre la xlet nello stato "paused".
* Dalla specifica mhp non è chiarissimo in termini implementativi cosa questo
* debba significare esattamente. Di solito viene inteso che la xlet debba
* liberare risorse che impegna in maneira esclusiva, fermare suoi therad non
* critici e rimuoversi dalla visualizzazione a schermo.
* Diversamente dagli altri metodi, da definizione non è concesso a pauseXlet
* di notificare una condizione di errore tramite eccezione, la specifica
* impone che il metodo termini e che sia in grado di garantire la transizione
* allo stato "paused".
*/
public void pauseXlet()
{
if (this.state != this.STATE_ACTIVE) {
System.out.println("Possibile condizione di errore: la Xlet non era " +
"nello stato \"Active\"");
}
this.thread.suspend();
this.state = this.STATE_PAUSED;
System.out.println("Xlet nello stato \"Paused\"");
}
/*
* Il seguente metodo deve portare la xlet enllo stato "destroyed" in cui
* essa deve aver fermato la sua esecuzione, rilasciato le risorse in uso ed
* aver in genere effetuato quanto necessario affinché la terminazione e
* rimozione dalla memoria da parte dell'ambiente di esecuzione non ponga
* condizioni problematiche, almeno per il resto del sistema e delle altre
* applicazioni; così come auspicabilmente anche per gli scopi della xlet
* stessa.
* Se l'argomento "unconditional" è true, la specifica impone che la xlet
* passi allo stato detroyed. se è false essa può "rifiutare"
* inoltrando una XletStateChangeException.
* (In caso di necessità l'ambiente di esecuzione invocherà nuovamente questo
* metodo, passando true come parametro).
*/
public void destroyXlet(boolean unconditional)
throws XletStateChangeException {
if (unconditional == true) {
this.thread.stop();
this.thread.destroy();
thread = null;
context = null;
state = STATE_DESTROYED;
System.out.println("Xlet nello stato \"Destroyed\"");
}
else {
throw new XletStateChangeException("Richiesta di passare allo stato " +
"\"Destroyed\" rifiutata.");
}
}
79
/*
* Metdo definito dall'intefaccia Runnable che la xlet deve implementare se si
* vogliono generare ulteriori thread oltre a quello in cui la xlet stessa è
* inizilaizzata. Viene qui usato per eseguire tutte le operazioni di
* interesse nel contesto in cui la presente xlet è definita.
*/
public void run() {
// si istanzia l'oggetto per lettura e parsing
// del file di configurazione.
ConfigFileParser cFP = new ConfigFileParser();
// Con il metodo load di tale oggetto si accedde al broadcast filesystem
// e si carica in memoria il contenuto del file corrispondente al path
// CONFIGFILEPATH relativo al mount point dell'object carousel.
// I byte letti vengono memorizzati come sequanza di caratteri secondo la
// codifica di carattere ConfigFileParser.UTF_8.
// Il metodo restituisce false se si verificano condizione di errore
// nell'accesso all'object carousel o al singolo file, o se il file cercato
// non è presente nell' object carousel.
System.out.println ("Caricamento del file di configurazione " +
this.CONFIGFILEPATH + " dall'object carousel ...");
if (cFP.load(this.context, this.CONFIGFILEPATH, ConfigFileParser.UTF_8) ==
false) {
System.out.println("Caricamento fallito.");
return;
}
System.out.println("Caricamento effettuato");
System.out.println();
// Si effettua il parsing del contenuto memorizzato dall'oggetto di tipo
// ConfigFileParser; l'array di stringhe restituito continene nell'ordine i
// 6 valori per: nome file su cui acquisire i dati, num. tel isp, nome utente,
// password, host a cui inviare i dati e relativa porta.
// Si ottine null se la sintassi del file di configurazione non è conforme
// a quella attesa o se non è stato caricato alcun file.
System.out.println("Parsing del file di configurazione ...");
String[] configParameters = cFP.parse();
if (configParameters == null) {
System.out.println("Sintassi non conforme a quella attesa.");
return;
}
String dataAcqFile_Filename = configParameters[0];
String ispPhoneNum = configParameters[1];
String userName = configParameters[2];
String password = configParameters[3];
String host = configParameters[4];
int port = 0;
try {
port = Integer.parseInt(configParameters[5]);
}
catch (Exception e) {
System.out.println("Il valore nel file di configurazione per la porta a" +
"cui connettersi, sull'host destinatario dei dati da" +
" acqusisire da RS-232, non rappresenta un intero");
return;
}
System.out.println("Parametri di configurazione memorizzati.");
System.out.println();
String orgId = (String)this.context.getXletProperty(XLETPROPNAME_ORGID);
String appId = (String)this.context.getXletProperty(XLETPROPNAME_APPID);
String sep = System.getProperty(SYSPROPNAME_SEPARATOR);
// Si crea l'oggetto per l'acqusizione dei dati, che verranno scritti nel
// file dataAcqFile_Filename nella home della applicazione, il cui path
// è definito da specifica mhp essere del tipo
// <AppsStorageRoot>/<OrgId>/<AppId>
DataRetrieverInterface dR = (DataRetrieverInterface)
new DataRetrieverOsmosys(orgId, appId,
dataAcqFile_Filename);
80
// Si innescano le operazioni di acqusizione dati
System.out.println("Acqusizione dati da intefraccia RS-232 nel file " +
orgId + sep + appId + sep + dataAcqFile_Filename + " ...");
if (dR.acquireData() == false) {
System.out.println("Acquisizione dati fallita.");
return;
}
System.out.println("Dati acquisiti.");
System.out.println();
// Si ottengono i dati precedentemente acquisiti e memorizzati
System.out.println("Lettura del file contenente i dati acquisiti ...");
byte[] bAData = dR.getData();
if(bAData == null){
System.out.println("Il file che dovrebbe contenere i dati acquisiti " +
"non esiste o la lettura del medesimo è fallita.");
return;
}
System.out.println("Lettura effettuata.");
System.out.println();
// Si crea l'oggeto per la gestione della connessione di rete, tipicamente
// utile a connetersi ad un ISP tramite connessione dial-up.
// La relativa classe usa internamente le api del package org.dvb.net.rc
// che implementanto le funzionalità che comunemente vanno sotto il nome di
// return channel. Si passano i parametri per il login ad un ISP.
NetConnection netC = new NetConnection(ispPhoneNum, userName, password);
// Effettua la connessione all'ISP, si ottiene true se l'operazione ha
// successo o anche nel caso verifichi l'esistenza di una connessione
// always-on già attiva.
System.out.println("Verifica disponibilità di una connessione di rete; " +
"connessione all' ISP se necessario " +
"(num.tel. / nome utente / password : " +
ispPhoneNum + " / "+ userName + " / " + password + ") ...");
if (netC.connect() == false) {
System.out.println("Connessione di rete non dipsonibile");
return;
}
System.out.println("Connessione di rete dipsonibile");
System.out.println();
// Si crea l'oggetto per la gestione di una connessione tcp.
TCPClient c = new TCPClient(host, port);
// Si effetua la connessione tcp con la controparte remota.
System.out.println("Creazione connessione tcp con " + host + ":" +
String.valueOf(port) + " ...");
if (c.connect() == false) {
System.out.println("Tentativo di instaurare una connessione tcp con " +
host + ":" + String.valueOf(port) + "fallito.");
return;
}
System.out.println();
// Si inviano i dati come sequenza binaria; si decodifica in una sequenza di
// caratteri l'input ricevuto in risposta dal server, usando la codifica
// di carattere TCPClient.SERVERMSG_CHARENC.
// Restituisce null se si verificano eccezioni, altrimenti il messaggio
// ricevuto in risposta all'invio dei dati.
String ans = c.handleConnection(bAData, TCPClient.SERVERMSG_CHARENC);
81
//
//
//
//
//
//
//
Opzione alternativa (relativo codice incluso nel presente commento):
si inviano i dati come sequenza di caretteri, tradotti in byte secondo
la codifica di carattere TCPClient.DATA_CHARENC; si decodifica in una
sequenza di caratteri l'input ricevuto in risposta dal server, usando la
codifica di carattere TCPClient.DATA_CHARENC.
String ans = c.handleConnection(stringData, TCPClient.DATA_CHARENC,
TCPClient.SERVERMSG_CHARENC);
if (ans != null) {
System.out.println("Dati Inviati. Risposta ricevuta dal server: " + ans);
System.out.println();
}
else {
System.out.println("Invio dei dati e/o ricezione di conseguente input " +
"non terminati regolarmente");
System.out.println();
}
// Chiusura della connessione usata e I/O streams assciati, con rilascio
// delle relative risorse
System.out.println("Terminazione connessione tcp con " + host + ":" +
String.valueOf(port) + " ...");
if (c.disconnect() == false) {
System.out.println("Chiusura della connessione tcp con " +
host + ":" + String.valueOf(port) + "fallita.");
}
System.out.println();
// Si termina la connessione remota; si ottiene true se l'operazione ha
// successo o anche se non è stata necessaria alcuna chiusura perchè
// si dipsone di una connessione always-on.
System.out.println("Chiusura connessione di rete se non di tipo always-on" +
" ...");
if (netC.disconnect() == false) {
System.out.println("Chiusura connessione di rete fallita.");
return;
}
System.out.println("Connessione chiusa (o chiusura non necessaria).");
}
}
82
4.9 - Conlcusioni
Si deve sottolineare che non è possibile individuare una soluzione di validità
generale, (quantomeno non in base solo alle API che un middlware MHP deve
esporre per conformità allo standard) che soddisfi la necessità di acquisire dati in
un terminale MHP attraverso connessione RS-232, almeno in teoria (e dal punto
di vista pratico è improbabile che tutti i produttori di STB con porta seriale
implementino funzionalità del tutto omogenee per l’utilizzo di questa).
Come già detto, innanzitutto la specifica MHP non impone la presenza di tale
connessione affinché un dispositivo sia conforme, ma, soprattutto, anche in caso
essa sia implementata, MHP non definisce alcuna interfaccia di programmazione
standard per la gestione della stessa.
Tuttavia, in base ai fatti noti ed alle possibilità più o meno concrete analizzate,
si può concludere che, seppure implementare una soluzione totalmente concreta
e di validità sostanzialmente generale non risulti praticabile solo sulla base della
documentazione relativa agli standard di interesse, acquisire dati in un terminale
MHP attraverso connessione seriale è sicuramente fattibile e non certo da
scartare a priori come un paradigma svantaggioso per “mancanza di generalità”.
Inviare i dati ad un sistema remoto attraverso internet risulta come prevedibile
agevole e codificabile in maniera versatile e del tutto universale grazie alle
risorse hardware e software e relative API che lo standard MHP impone siano a
disposizione dello sviluppatore. Lo stesso si può dire per la eventuale necessità
di memorizzare temporaneamente tali dati in modo persistente nel terminale
MHP, con l’unico serio ostacolo nel caso in cui la natura stessa di questi dati e le
funzionalità del dispositivo da connettere alla RS-232 comportino una
dimensione per una singola acquisizione che metta in crisi la capacità di storage
per le applicazioni MHP garantita da specifica, di soli 4096 byte. Il che sarebbe
tuttavia facilmente risolvibile dotandosi di un STB dotato di maggiori risorse,
cosa non infrequente, tra l’altro, nel caso di modelli dedicati allo sviluppo.
Utilizzare le potenzialità di un STB MHP in grado di acquisire dati da un’altro
dispositivo, ed in generale della “piattaforma” per la DTT, allo scopo di fornire
anche servizi che esulano dagli ambiti dell’intrattenimento, come la già citata
tipologia relativa tele-monitoraggio di parametri biometrici, risulta quindi un
paradigma che può essere tenuto in effettiva considerazione in fase di progetto
di tali servizi, avendo cura di valutarne attentamente l’implementazione, per le
ragioni precedentemente analizzate, in base allo specifico servizio e agli
specifici dispositivi da utilizzare “sul campo”.
83
5 – Fonti di Documentazione in Formato Elettronico e
Bibliografia
http://www.mhp.org
http://www.dvb.org
http://www.davic.org
http://www.havi.org
http://java.sun.com
http://www.mhp-interactive.org
http://www.mhp-knowledgebase.org/
Steven Morris, Anthony Smith-chaigneau “Interactive TV Standards”, Focal
Press
Herve Benoit “Digital Television”, Focal Press
Bruce Eckel, “Thinking in Java” 3a edizione, Prentice-Hall
84