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