MokaByte 70- Gennaio 2003 
JSP e Web User Interface
JSTL - La JSP Standard Tag Library (IV parte)
di
Lavinio Cerquetti
Completiamo lo studio della JSP Standard Tag Library analizzandone le funzionalità di internazionalizzazione e di integrazione con database relazionali

Introduzione
Il nostro viaggio all'interno della JSP Standard Tag Library [1], iniziato con una presentazione delle sue funzionalità e del nuovo ruolo del framework JSP [2] nel quadro di applicazioni Java Enterprise e proseguito con lo sviluppo e l'evoluzione di una semplice Web Application - denominata NetView - giunge con l'articolo di questo mese alla sua conclusione.
In particolare, sperimentate nel numero precedente le funzionalità offerte dai componenti XML di JSP Standard Tag Library, termineremo la nostra panoramica dei servizi offerti da JSTL analizzandone i componenti di internazionalizzazione e di integrazione con database relazionali, di cui ci avvarremo per implementare le funzionalità aggiuntive richieste a NetView dal nostro ipotetico committente.
Per la massima comprensione dei contenuti che andremo a trattare si consiglia di fare frequente riferimento sia agli articoli precedenti [3], [4] e [5] sia all'applicazione NetView, i cui sorgenti commentati sono liberamente disponibili e scaricabili da [5].

 

Funzionalità SQL di JSTL
I componenti SQL di JSTL - come è lecito attendersi dalle nostre precedenti esperienze con questa libreria - sono pochi e chiari, e la loro comprensione risulterà immediata a chiunque abbia utilizzato, almeno una volta nella vita, le JDBC API.

Il componente SQL va innanzitutto specificato nel deployment descriptor come segue:

<taglib>
  <taglib-uri>http://java.sun.com/jstl/sql</taglib-uri>
  <taglib-location>/WEB-INF/sql.tld</taglib-location>
</taglib>

Le pagine che utilizzano i tag SQL dichiarano tale Tag Library con la direttiva taglib:

<%@ taglib prefix="sql" uri="http://java.sun.com/jstl/sql" %>

La connessione al database SQL di NetView viene specificata nel deployment descriptor come default per l'intera applicazione tramite le seguenti righe:

<context-param>
  <param-name>
    javax.servlet.jsp.jstl.sql.dataSource
  </param-name>
  <param-value>
    jdbc:<dataSource>,<driverClass>,<userName>,<password>
  </param-value>
</context-param>

I componenti SQL permettono di procedere alla configurazione delle connessioni tramite una pluralità di metodi, e precisamente:

  • Ogni tag SQL può, nell'attributo dataSource, specificare una connessione attraverso il suo percorso JNDI o tramite la sintassi, da noi utilizzata, avente la forma "jdbc:<dataSource>,<driverClass>,<userName>,<password>"; qualora il tag non possieda attributo dataSource, verrà utilizzata la connessione di default;
    in alternativa ogni tag SQL può, sempre nell'attributo dataSource, fare esplicito riferimento ad un oggetto DataSource impostato da uno strato di codice Java superiore o tramite un tag <sql:setDataSource>;
  • Il tag <sql:setDataSource> rende disponibile un nuovo oggetto DataSource alla pagina corrente, eventualmente impostandolo come nuova connessione di default; tale oggetto può venire ottenuto da uno strato esterno di codice, tramite un riferimento JNDI, tramite la sintassi da noi utilizzata o grazie all'utilizzo diretto degli attributi driver, url, user e password del tag;
  • una connessione di default può venire impostata a livello di applicazione - come nel nostro caso - direttamente nel deployment descriptor, tramite il suo percorso JNDI o la sintassi "jdbc:<dataSource>,<driverClass>,<userName>,<password>" da noi utilizzata.

È rimarchevole come la gestione delle connessioni SQL di JSTL risulti perfettamente integrata nel contesto J2EE e permetta agli sviluppatori JSP di sfruttare - ponendosi in quest'ambito esattamente allo stesso livello di astrazione degli strati EJB e JDO - le connessioni preimpostate dall'Application Deployer in sede JNDI, condividendo in maniera ottimizzata con gli altri componenti della piattaforma Java Enterprise connessioni JDBC di qualsiasi natura e traendo vantaggio dalle funzionalità automatiche di connection pooling e distributed transactions della piattaforma J2EE.

Le funzionalità SQL vengono implementate tramite i seguenti tag:

  • Il tag <sql:query> permette di eseguire una query SQL (tipicamente tramite lo statement select) eventualmente parametrica ritornandone il risultato in una variabile JSTL di tipo javax.servlet.jsp.jstl.sql.Result; il testo della query può venire fornito come attributo o come corpo del tag;
  • il tag <sql:update> permette di inviare al server SQL un comando di aggiornamento (quali insert, update e delete); esattamente come per il tag precedente l'aggiornamento può avvenire in modalità parametrica, ed il testo del comando può venire specificato come attributo ovvero come corpo del tag; il risultato - in stile JDBC, il numero di righe inserite, modificate o cancellate a seguito del comando - viene archiviato in una variabile JSTL;
  • una serie di tag <sql:query> e <sql:update> possono venire innestati all'interno di un tag <sql:transaction> al fine di raggrupparli in una transazione; in base all'esito della transazione il tag <sql:transaction> provvederà automaticamente al commit o al rollback;
    eventuali parametri a query ed aggiornamenti vengono forniti tramite tag <sql:param> innestati all'interno del relativo tag <sql:query> o <sql:update>.

Il risultato di una query viene fornito sotto forma di interfaccia javax.servlet.jsp.jstl.sql.Result, le cui principali proprietà sono:

  • rows: rappresenta le righe della tabella risultato della query, sotto forma di SortedMap in cui le chiavi sono costituite dai nomi delle colonne, ed i valori dai valori delle rispettive colonne;
  • rowCount: il numero di righe della tabella risultato;
  • columnNames: i nomi delle colonne della tabella risultato, sotto forma di vettore di stringhe disposte secondo l'ordine con cui le colonne sono state ritornate dal server SQL.

Implementazione delle funzionalità di visualizzazione del traffico
Anche in questo caso le modifiche al codice Java sono pressoché inesistenti, e si limitano alla definizione di un nuovo Dispatcher.
La prima sezione della View querytraffic.jsp, analogamente alla View querylogs.jsp, fornisce all'utente una ricapitolazione dei criteri di ricerca ed ordinamento attuali, eventualmente impostando dei valori di default:

<c:if test="${empty order}">
<c:set var="order" value="${'trd_date desc'}" />
</c:if>
.
.
<c:choose>
<c:when test="${order=='trd_date desc'}">
Data
</c:when>
<c:when test="${order=='node_id,trd_date desc'}">
Nodo e data
</c:when>
<c:when test="${order=='trd_mbytes desc'}">
Traffico
</c:when>
</c:choose>
.
.

Nell'esempio la variabile order contiene il criterio di ordinamento attuale in formato SQL; il primo spezzone di codice, se tale variabile è vuota - vale a dire nulla o di lunghezza zero - provvede a valorizzarla per default con l'ordinamento per data decrescente.
Il contenuto di tale variabile viene quindi presentato all'utente in forma leggibile.

Successivamente viene eseguita la query, tenendo conto dei parametri di ricerca e di ordinamento impostati dall'utente:

<c:choose>
<c:when test="${!empty node}">
<sql:query var="trafficData">
select node_id,trd_date,trd_mbytes
from traffic_data
where node_id=?
order by <c:out value="${order}" />
<sql:param value="${node.id}" />
</sql:query>
</c:when>
<c:otherwise>
<sql:query var="trafficData">
select node_id,trd_date,trd_mbytes
from traffic_data
order by <c:out value="${order}" />
</sql:query>
</c:otherwise>
</c:choose>

Se l'utente non ha richiesto di restringere la ricerca ad un nodo particolare, vengono lette tutte le righe dalla tabella di traffico; altrimenti la selezione viene limitata alle righe facenti capo al nodo specificato attraverso una query parametrica, la cui sintassi è assai simile a quella consueta in ambito JDBC.
In entrambi i casi la selezione tiene conto dell'ordinamento impostato; si noti che, esattamente come in JDBC, l'ordinamento non può venire impostato come parametro, ma deve tradursi nella produzione di una query sql personalizzata. In contesto JSTL ciò si traduce nella generazione di un tag <sql:query> a contenuto variabile tramite il tag <c:out>.
Il risultato della query è ora disponibile nella variabile trafficData, sulla cui proprietà rows è possibile iterare, ottenendo una ad una le righe della nostra tabella risultato:

<c:set var="totalTraffic" value="${0}" />
.
.
<c:forEach items="${trafficData.rows}" var="cursor">
<tr>
<td>
<a href=
"/netview/main?action=nodeform&subaction=edit&id=<c:out value='${cursor.node_id}' />">
<c:out value="${cursor.node_id}" />
</a>
</td>
<td><c:out value="${cursor.trd_date}" /></td>
<td><c:out value="${cursor.trd_mbytes}" /></td>
</tr>
<c:set var="totalTraffic"
value="${totalTraffic+cursor.trd_mbytes}" />
</c:forEach>
.
.
<c:out value="${totalTraffic}" /> MB

Ancora una volta il codice JSTL si dimostra di assai facile lettura. Si noti come, contestualmente alla lettura delle righe, i totali giornalieri di traffico vengano sommati nella variabile totalTraffic, inizializzata a 0 prima del tag forEach e visualizzata nell'ultima riga della tabella.

A pie' di pagina viene quindi prodotto il form che permette all'utente di avviare una nuova ricerca; il codice relativo è simile a quello della View querytraffic.jsp, con la sola differenza che questa volta la list box dei nodi è limitata ai soli router, l'unica categoria di dispositivi in grado di generare dati di traffico.

 

Internazionalizzazione e localizzazione
Per internazionalizzazione si intende l'astrazione di un'applicazione dalle caratteristiche culturali e geografiche delle piattaforme client.
Al processo di internazionalizzazione si accoppia invariabilmente la localizzazione, vale a dire l'adattamento - tipicamente a run-time - di un'applicazione alla caratteristiche di una specifica regione geografica.

Ai fini dell'internazionalizzazione si possono identificare due macrocategorie di informazioni:

  • Informazioni dinamiche, per le quali l'internazionalizzazione consiste nello specificare la forma in cui esse vengono presentate all'utente; esempi tipici sono costituiti da numeri e date, la cui formattazione varia di paese in paese, mentre l'informazione in quanto tale rimane immutata;
  • informazioni statiche: in questo caso è l'informazione in sé ad essere funzione del processo di internazionalizzazione; ricadono in questa categoria tutti i messaggi di un'applicazione, che devono variare in ogni regione geografica in base alla lingua dell'utente.

L'insieme delle informazioni statiche e delle forme delle informazioni dinamiche che caratterizzano ogni regione geografica vengono denominate 'locale' (da intendersi come termine inglese). Si noti che per un unico paese, ovvero per un'unica lingua, possono essere definiti più 'locale'. Questo è senz'altro il caso della lingua inglese, per la quale oltre all'atteso 'locale' en (lingua inglese / regione geografica non specificata) esiste tutt'una serie di 'locale' specifici, quali en_US (inglese / USA) ed en_AU (inglese / Australia), ma anche della nostra, più modesta, lingua italiana, per la quale oltre al 'locale' it (lingua italiana / Italia) è definito anche il 'locale' it_CH (lingua italiana / Svizzera).

L'internazionalizzazione di un'applicazione consiste nell'identificare ed isolare tutte le sezioni di un'applicazione dipendenti dalla regione geografica del client, sostituendo ad informazioni statiche e forme di presentazione dinamiche riferimenti a dei gruppi di risorse esterni, definiti 'bundle'.
Nel mondo Java un 'bundle' è costituito da un file di testo in formato ASCII, il cui nome è costituito da un prefisso, usualmente legato all'applicazione e denominato basename, e dal nome del 'locale' cui tale file si riferisce. Esso contiene un elenco di informazioni statiche, vale a dire di messaggi, secondo la seguente sintassi:

<chiave>=<valore>

<chiave> è il nome simbolico del messaggio, a cui l'applicazione si può riferire indipendentemente dalla piattaforma geografica di esecuzione, mentre <valore> è il contenuto del messaggio nel 'locale' cui il file fa riferimento.
In ambito Java usualmente le forme di presentazione dinamiche sono preconfigurate e vengono attivate automaticamente dal run-time in base alle caratteristiche del client.

In contesti Web la piattaforma geografica del client - vale a dire il 'locale' - viene comunicata all'Application Server, e quindi all'applicazione, direttamente dal browser dell'utente (Edit->Preferences->Navigator->Languages in Mozilla e Strumenti->Opzioni Internet->Lingue sotto Internet Explorer).

Funzionalità di internazionalizzazione di JSTL
L'ultimo componente di JSTL che ci rimane da analizzare è la libreria di internazionalizzazione, detta anche FMT o I18N.

Come per gli altri componenti, anche essa va specificata nel deployment descriptor:

<taglib>
<taglib-uri>http://java.sun.com/jstl/fmt</taglib-uri>
<taglib-location>/WEB-INF/fmt.tld</taglib-location>
</taglib>

Per avvalersi dei servizi di internazionalizzazione una pagina deve contenere l'apposita dichiarazione:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jstl/fmt" %>

Il deployment descriptor accetta tre parametri di configurazione, da definirsi come context-param, per la libreria FMT:

javax.servlet.jsp.jstl.fmt.localizationContext: il basename del 'bundle' utilizzato per default dall'applicazione;
javax.servlet.jsp.jstl.fmt.fallbackLocale: il 'locale' di default da utilizzare se un client non fornisce nessuna informazione a riguardo o se il 'locale' richiesto dal client non è disponibile;
javax.servlet.jsp.jstl.fmt.locale: questo parametro, di utilizzo raro, permette di forzare un 'locale' ignorando effettivamente le informazioni di localizzazione fornite dal client.

I principali tag votati alle funzionalità di internazionalizzazione sono i seguenti:

  • Il tag <fmt:message> carica dal 'bundle' specificato nell'attributo omonimo il messaggio la cui chiave è contenuta nell'attributo key, visualizzandolo o archiviandolo in una variabile JSTL; qualora l'attributo bundle non venga fornito viene utilizzato il 'bundle' corrente; la libreria FMT gestisce anche messaggi parametrici, vale a dire messaggi in cui una parte del testo può variare a run-time;
  • <fmt:formatNumber>: formatta un numero in base alle caratteristiche del 'locale' corrente ed alla natura delle informazioni rappresentate dal numero stesso (numero, valuta o percentuale); tale tag dispone inoltre di diversi attributi, che permettono di specificare il formato e la maschera di output delle informazioni;
  • <fmt:parseNumber>: svolge l'azione opposta rispetto al tag precedente, effettuando il parsing di una stringa contenente un numero in formato localizzato e visualizzando o memorizzando il valore corrispondente in una variabile JSTL;
  • <fmt:formatDate> e <fmt:parseDate>: in maniera analoga ai due tag precedenti, consentono la formattazione ed il parsing localizzato di date;
  • <fmt:setTimeZone>: archivia in una variabile o imposta come default una time zone;
    <fmt:timeZone>: processa il contenuto del corpo del tag in base ad una precisa impostazione di time zone;
  • <fmt:param>: questo tag, innestato in un tag <fmt:message>, specifica il valore di un parametro di messaggio;
  • il tag <fmt:bundle> imposta un 'bundle' - specificato nell'attributo basename - come default all'interno del corpo del tag, il cui contenuto verrà localizzato in base al basename fornito ed al 'locale' corrente; l'impostazione del parametro javax.servlet.jsp.jstl.fmt.localizationContext - da noi utilizzato - ottiene un effetto analogo, ma valido in maniera globale per tutte le pagine JSP dell'applicazione;
  • <fmt:setBundle>: questo tag imposta il parametro javax.servlet.jsp.jstl.fmt.localizationContext;
  • <fmt:setLocale>: questo tag, di utilizzo sporadico, imposta il parametro javax.servlet.jsp.jstl.fmt.locale.

Implementazione delle funzionalità di internazionalizzazione
Per gli scopi di questo articolo limiteremo l'internazionalizzazione alla sola funzionalità di visualizzazione dei dati del traffico; la procedura per le altre parti dell'applicazione sarebbe in concreto esattamente identica.
Per maggior chiarezza introdurremo una nuova azione, querytrafficintl, che produce gli stessi report di traffico dell'azione querytraffic, ma in versione localizzata.
Come nei casi precedenti le uniche modifiche al codice Java sono costituite dalla definizione di un nuovo Dispatcher e dalla sua dichiarazione nella classe FrontControllerServlet.
Procediamo quindi all'identificazione delle risorse da internazionalizzare nella View querytrafficintl.jsp:

  • Le risorse dinamiche, per cui l'internazionalizzazione si applica alle forme di presentazione, sono costituite dai dati variabili, vale a dire nel nostro caso dalle date e dai totali di traffico giornalieri recuperati dal database SQL e dal totale di traffico visualizzato a pie' di report; la visualizzazione di tali dati viene quindi racchiusa in tag <fmt:formatDate> e <fmt:formatNumber>;
  • Le risorse statiche, di cui dobbiamo internazionalizzare i contenuti, sono costituite da tutti i messaggi prodotti dalla nostra applicazione, che vanno estrapolati dalla View, tradotti in tutti i 'locale' che si desidera supportare ed archiviati in 'bundle' separati; al posto dei messaggi statici si inseriscono nella View dei tag <fmt:message>.

A livello di deployment descriptor configuriamo come 'locale' di fallback en_US e come basename di default per i 'bundle' di NetView la stringa 'messages'.

<context-param>
  <param-name>
    javax.servlet.jsp.jstl.fmt.fallbackLocale
  </param-name>
  <param-value>
    en_US
  </param-value>
</context-param>

<context-param>
  <param-name>
    javax.servlet.jsp.jstl.fmt.localizationContext
  </param-name>
  <param-value>
    messages
  </param-value>
</context-param>

Decidiamo di supportare i 'locale' it ed en_US, creando i due 'bundle' messages_it.properties e messages_en_US.properties, che archiviamo in WEB-INF/classes. A titolo di esempio riproduciamo il contenuto del 'bundle' messages_it.properties:

titlequerytrafficintl=Visualizzazione traffico I18N
allnodes=(Tutti i nodi)
node=Nodo
ordering=Ordinamento
searchcriteria=Ricerca
date=Data
nodedate=Nodo e data
traffic=Traffico
mbytes=Megabyte trasferiti
newsearch=Nuova ricerca
nocriterium=(Nessun criterio)
search=Cerca

Si noti che, avendo definito un 'locale' di fallback, abbiamo la certezza che ogni client della nostra applicazione sarà identificato da un 'locale'; in caso contrario dovremmo definire un 'bundle' per il 'locale' nullo, vale a dire un file message.properties con contenuti analoghi a quelli mostrati.

Le novità della View querytrafficintl.jsp rispetto alla View querytraffic.jsp consistono semplicemente nell'astrazione delle informazioni tramite i tag FMT di JSTL; osserviamo ad esempio l'aspetto della tabella contenente i risultati della query prima in querytraffic.jsp:

<tr>
<th>Nodo</th>
<th>Data</th>
<th>Megabyte trasferiti</th>
</tr>
<c:forEach items="${trafficData.rows}" var="cursor">
<tr>
<td>
<a href=
"/netview/main?action=nodeform&subaction=edit&id=<c:out value='${cursor.node_id}' />">
<c:out value="${cursor.node_id}" />
</a>
</td>
<td><c:out value="${cursor.trd_date}" /></td>
<td><c:out value="${cursor.trd_mbytes}" /></td>
</tr>
<c:set var="totalTraffic"
value="${totalTraffic+cursor.trd_mbytes}" />
</c:forEach>

e quindi in querytrafficintl.jsp:

<tr>
<th><fmt:message key="node" /></th>
<th><fmt:message key="date" /></th>
<th><fmt:message key="mbytes" /></th>
</tr>
<c:forEach items="${trafficData.rows}" var="cursor">
<tr>
<td>
<a href=
"/netview/main?action=nodeform&subaction=edit&id=<c:out value='${cursor.node_id}' />">
<c:out value="${cursor.node_id}" />
</a>
</td>
<td>
<fmt:formatDate value="${cursor.trd_date}"
type="date" dateStyle="full"/>
</td>
<td>
<fmt:formatNumber value="${cursor.trd_mbytes}"
type="number" pattern="#,##0"/>
</td>
</tr>
<c:set var="totalTraffic"
value="${totalTraffic+cursor.trd_mbytes}" />
</c:forEach>

Come si può notare i messaggi statici sono stati sostituiti da tag <fmt:message> e la visualizzazione di date e numeri avviene per mezzo di tag <fmt:formatDate> e <fmt:formatNumber>.
Nel primo caso gli attributi type e dateStyle, impostati rispettivamente a "date" e "full", richiedono la visualizzazione dei soli dati di giorno, mese ed anno in forma 'lunga', vale a dire con nomi completi di giorno e mese.
I dati del traffico vengono invece presentati, secondo quanto specificato dall'attributo pattern, tramite un numero di cifre intere da 1 a 4 con eventuale separatore di migliaia. Tale informazione viene caratterizzata come semplicemente numerica ponendo l'attributo type al valore 'number'; le altre possibilità sarebbero state 'currency' e 'percentage', per indicare che il valore è da intendersi rispettivamente come valuta o percentuale.

Si noti infine che il processo di internazionalizzazione adottato, consistente nell'adozione dei 'bundle' e nella completa astrazione di tutte le informazioni statiche, non è l'unico possibile.
Questo processo ha lo svantaggio di nascondere totalmente i contenuti reali delle pagine Web ai grafici, complicando notevolmente le fasi di progettazione, impaginazione e manutenzione del layout HTML dell'applicazione.
Un'alternativa è data dalla realizzazione di versioni differenti delle pagine JSP in funzione dei diversi 'locale' supportati, utilizzando un FrontControllerServlet esteso per ritornare al client una View specifica in base alle proprie caratteristiche geografiche.
Quest'impostazione, che privilegia il lavoro dei grafici e semplifica il lavoro di traduzione per applicazioni Web caratterizzate da moli di testo significative, ha tuttavia l'effetto collaterale di produrre un elevato numero di View duplicate, complicando le fasi di manutenzione ed aggiornamento del codice.

Nella pratica è consigliabile un approccio 'ragionato' che si collochi a metà strada tra questi due estremi e che faccia uso delle funzionalità di inclusione parametriche di JSP per mantenere un insieme unico di View ed isolare la gran parte delle informazioni testuali in file HTML esterni, direttamente gestibili dai grafici Web, prevalentemente statici e duplicati per i diversi 'locale' supportati.

 

Conclusioni
Con l'analisi e la sperimentazione sul campo dei suoi componenti di internazionalizzazione e di integrazione con database relazionali si conclude la nostra trattazione di JSP Standard Tag Library.
L'introduzione di questa libreria ha rappresentato per il framework Java Server Pages una vera e propria rivoluzione, al punto che non è errato asserire che esistano due JSP: un JSP ante-JSTL ed un JSP post-JSTL.
Il successo di JSP Standard Tag Library e del Java Server Pages 'moderno' - della cui versione 2.0 è già disponibile il Proposed Final Draft [6] - viene significativamente confermato dalle statistiche WebCraft per l'anno 2002 [7], che indicano in JSP il linguaggio di scripting Web in più rapida ascesa.
Nel prossimo articolo inizieremo l'analisi di una nuova Tag Library, con tutta probabilità destinata a rappresentare uno standard in tema di Java Web User Interface: stiamo parlando di Java Server Faces, il framework Java per la realizzazione di interfacce HTML componentizzate, state-aware, riusabili ed event-driven.

 

Bibliografia
[1] JSTL - JSP Standard Tag Library: http://java.sun.com/products/jsp/jstl
[2] JSP - JavaServer Pages: http://java.sun.com/products/jsp
[3] Lavinio Cerquetti: "JSP 1.2: La JSP Standard Tag Library - I parte", Mokabyte N. 67 - Ottobre 2002
[4] Lavinio Cerquetti: "JSP 1.2: La JSP Standard Tag Library - II parte", Mokabyte N. 68 - Novembre 2002
[5] Lavinio Cerquetti: "JSP 1.2: La JSP Standard Tag Library - III parte", Mokabyte N. 69 - Dicembre 2002
[6] Java Server Pages 2.0 Specification - Proposed Final Draft: http://jcp.org/aboutJava/communityprocess/first/jsr152/
[7] Statistiche NetCraft anno 2002: http://www.netcraft.com/survey

 

Risorse
Il codice dell'applicazione presentata nell'articolo (NetView versione 1.1) è interamente scaricabile da [5].

Lavinio Cerquetti si occupa di design e sviluppo del software in ambienti distribuiti ed in architetture J2EE multi-tier. Può essere contattato all'indirizzo di e-mail lcerquetti@mokabyte.it

MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it