Introduzione
Fra le novità introdotte in Servlet API 2.3 c'è
il filter; un filtro è un pre-processore delle
richieste che arrivano ad una servlet/jsp e/o un post-processore
di risposte generate da una servlet/jsp. E' dunque possibile
intercettare, esaminare, modificare gli oggetti request
e response legati ad una servlet o jsp.
E' giusto ricordare che un filtro non è una servlet,
ma è un componente invocato dal container J2EE
quando un client richiede una risorsa per la quale il
filtro è stato configurato.
La raccolta di dati e la successiva organizzazione consentono
di produrre statistiche di un sito web; con il supporto
di tool grafici, come ad esempio JCharts[1], è
possibile produrre grafici da mostrare all'utente.
Ambiente
e scopo del filtro
Il filtro, pur non essendo una servlet, ha una struttura
simile; si tratta, infatti, di una interfaccia (javax.servlet.Filter)
che espone 3 metodi:
- public
void init(FilterConfig filterConfig)
- public
void doFilter(final ServletRequest request, final
ServletResponse response, FilterChain chain)
- public
void destroy()
I
metodi seguono lo stesso lifecycle delle servlet; il
comportamento di un filtro è dettato dall'implementazione
del metodo doFilter.
Per questo esempio, voglio raccogliere cinque informazioni:
- l'ora
corrente
- il
tempo di computazione
- URL
chiamato dal client
- Numero
di byte restituiti al client
- Il
nome dell'utente
Quasi
tutte le informazioni sono presenti nell'oggetto request
oppure disponibili a run-time nel filtro; l'unica informazione
non presente in request ma presente in response è
il numero di bytes restituiti al client; infatti in
base al tipo richiesta fatta può variare notevolmente
l'output generato.
A scopo di esempio ho creato con MsAccess un piccolo
database contenente una tabella in cui ho inserito dei
libri e l'utente può eseguire delle ricerche
per titolo del libro. A seguito della ricerca, in output
vengono forniti un codice del libro, il titolo, l'autore
e la casa editrice.
L'applicazione riconosce solo 2 utenti; viene fatta
una autenticazione light e posto in sessione un oggetto
contenente nome e cognome che l'utente ha fornito al
momento dell'autenticazione.
Il filtro è impostato per lavorare sull'operazione
di ricerca dei libri (una servlet) e sulla chiamata
di una pagina di errore (errore.html). Ogni volta che
viene fatta una ricerca di un libro o l'utente ha fornito
nome e/o cognome errati, il filtro viene invocato dal
container.
I dati raccolti vengono salvati in un file di testo
e successivamente, a richiesta da console DOS, possono
essere salvati in un'apposita tabella creata nel database.
In maniera asincrona, è possibile vedere una
rappresentazione grafica dei dati memorizzati nel database.
Sono visibili alcuni tipi di grafici che rappresentano
il numero di accessi alle pagine.
A scopo di esempio c'è grafico a torta (Pie Chart)
e un istogramma semplice(Bar Chart) e un istogramma
con più categorie(Clustered Bar).
Per poter utilizzare il database dall'applicazione java
è necessario impostare lo stesso come fonte dati
ODBC; ciò si ottiene seguendo il percorso (in
win2000) Start, Settings, Control Panel, Administrative
Tools,
Data Sources (ODBC). Una volta aperta l'icona, si selezioni
la scheda System DSN e si aggiunga una nuova fonte dati;
in Data Source Name (o nuova fonte dati) si immetta
'bibliotecaSTAT', quindi si faccia clic su pulsante
Select (o seleziona) per impostare il percorso di ricerca
del database.
Costruzione del filtro
Come già detto quasi tutti i dati sono presenti
nell'oggetto request; vediamo come recuperarli:
//tempo
iniziale
long before = System.currentTimeMillis();
nameURI = ((HttpServletRequest)servletRequest).getRequestURI();
//recupero l'oggetto contenente i dati dell'utente
userData = (UserVO)session.getAttribute("userData");
//tempo finale
long after = System.currentTimeMillis();
Per
scrivere i dati si è utilizzato Log4j [3]; in
tal modo non è necessario compiere sforzi per
ottenere la data e l'ora corrente. Usare Log4j in modalità
DailyRollingFileAppender consente di generare un nuovo
file ogni giorno; il vecchio file viene rinominato appendendo
il suffisso del giorno in formato yyyy-MM-dd. All'interno
del file, invece, per ogni inserita, log4j inserisce
anche la data nel formato da me scelto: 'HH:mm:ss'.
Le impostazioni di Log4j sono contenute in un apposito
file di configurazione denominato log4j.lcf presente
nella directory classes del progetto; ciascuno dovrà
indicare una directory di output dove verrà scritto
il file delle statistiche.
Nel codice del filtro ci sarà una chiamata a
Log4j di questo tipo:
//log
delle info: tempo-url-Bytes-nome utente
logger.info( (after-before) + "-"
+ nameURI + "-"
+ numBytes + "-"
+ (userData!=null?
userData.getSurname()+" "+userData.getName():""));
che
corrisponde nel file di output a:
21:05:40
1182-/biblioteca/servlet/mysite.statistiche.operation.RicercaLibri-378-Rossi
Mario
Resta
ancora insoluto il problema di come contare i byte restituiti
al client.
Il trucco, se così si può dire, consiste
nel passare al metodo doFilter una versione customizzata,
detto anche wrapper, dell'oggetto response. Questo wrapper
deve nascondere, o wrappare, gli oggetti originari presenti
in response e fornire un'implementazione per eseguire
l'attività di streaming.
Il wrapper dell'oggetto response corrisponde alla classe
MyResponseWrapper:
public
class MyResponseWrapper extends HttpServletResponseWrapper
{
protected ServletOutputStream stream = null;
protected PrintWriter pw = null;
protected HttpServletResponse httpServletResponse;
public
MyResponseWrapper(HttpServletResponse httpServletResponse)
throws IOException{
super(httpServletResponse);
this.httpServletResponse = httpServletResponse;
//creo
un mio oggetto per fare streaming
stream = new MyOutputStream(
httpServletResponse.getOutputStream());
pw = new PrintWriter(stream);
}
// Il metodo ritorna lo stream customizzato
public ServletOutputStream getOutputStream()
throws IOException {
return stream;
}
public PrintWriter getWriter() throws IOException
{
return pw;
}
}
Vediamo
ora l'implementazione della classe per fare streaming.
La classe ridefinisce i metodi utili a raggiungimento
dello scopo di conteggiare i byte; in pratica viene
fornita una nuova logica per i metodi di scrittura write(
)
e i metodi di gestione dell'output flush() e close()
public
class MyOutputStream extends ServletOutputStream{
//implemento i metodi write, close e flush.
private long numBytes = 0;
public MyOutputStream(OutputStream outStream)
{
inStream = outStream;
baStream = new ByteArrayOutputStream();
}
public void flush() throws java.io.IOException{
//conto i byte spediti
numBytes += baStream.size();
if (numBytes != 0){
if (!closed){
inStream.write(
baStream.toByteArray());
inStream.flush();
baStream
= new ByteArrayOutputStream();
}
}
}
}
Come è possibile notare nel codice della classe
MyOutputStream, nel metodo flush() eseguo il conteggio
dei byte che vengono passati al client, invio i dati
presenti nello stream e creo un nuovo ByteArrayOutputStream
in modo tale da poter inserire altri dati e poter aggiornare
correttamente il numero di byte.
Oltre ad aver fatto override dei metodi della classe
ServletOutputStream, ho anche aggiunto un metodo getNumBytes()
che restituisce semplicemente il numero dei byte conteggiati.
Visualizzazione
delle statistiche
Una volta che nella tabella delle statistiche ci sono
dei dati è possibili iniziare a visualizzare
degli esempi di grafici di statistiche. Per questi esempi
è stato utilizzato il tool JCharts [1]; sicuramente
non è l'unico tool java che consente di fare
grafici, ne esistono anche di più potenti come
ad esempio JFreeChart [2], ma il vantaggio di JCharts
risiede, secondo me, nell'abbondanza di spiegazioni
ed esempi per chi ha il primo contatto con un tool di
questo tipo.
A scopo di esempio vengono presentati un grafico a torta
(Pie Chart), un istogramma semplice (Bar Chart) e un
istogramma con più categorie(Clustered Bar).
Questi grafici sono una rivisitazione dei file di esempio
presenti in JCharts; per questo motivo non verrà
dato spazio ad una spiegazione della costruzione dei
grafici perchè altro non sarebbe che un'operazione
di cut & paste della documentazione. Sicuramente
cosa migliore è soffermarsi sulla tipologia di
logica che è possibile implementare per ottenere
i grafici; infatti, dopo aver recuperato i dati da visualizzare,
è possibile operare in due modi distinti:
- creare
l'immagine e spedirla direttamente al client
- creare
l'immagine, salvarla in HttpSession, e dal client
invocare un'apposita servlet che recuperi l'immagine
e ne faccia lo streaming.
Vediamo
pregi e difetti delle 2 soluzioni.
Nel primo caso, l'immagine creata viene spedita subito
al client e quindi ho una minore occupazione di memoria,
soprattutto in HttpSession. Si presenta però
il problema di non poter visualizzare altre informazioni
o contenuti.
Nel secondo caso, invece, utilizzando una jsp posso
richiamare una servlet che faccia streaming dell'immagine
salvata, usando il nome completo della servlet come
valore dell'attributo src del tag img presente nella
pagina jsp:
<img
src="statistiche.operation.OutputServlet"
useMap="#chartMap">
in
questo caso occupo per un periodo più lungo la
memoria, in compenso ho il totale controllo della pagine
jsp.
Per completezza di informazione esiste anche una terza
possibilità, cioè creare l'immagine e
salvarla su disco ed avere nella jsp un riferimento
all'immagine salvata; questa opzione mi sembra presentare
molti svantaggi (definizione del nome dell'immagine,
occupazione del disco, schedulazione di processi per
la rimozione di vecchie immagini).
Per tutti e tre i casi, comunque, sono presenti degli
esempi; la logica di costruzione del grafico è
simile in tutti i quasi, mentre si diversifica la logica
per visualizzare l'immagine del grafico.
Infatti nel caso di spedizione del grafico direttamente
al client (caso 1):
//oggetto contenente il grafico
AxisChart axisChart =
//spedizione del grafico al client
JPEGEncoder13.encodeServlet(axisChart, 1.0f, response);
nel
caso di salvataggio del grafico in HttpSession (caso
2):
//
oggetto contenente il grafico
PieChart2D pieChart2D =
//salvataggio dell'oggetto in sessione
req.getSession( true ).setAttribute("CHART",
pieChart2D );
nell'
ultimo caso, quello di salvataggio su disco (caso 3)
//
oggetto contenente il grafico
AxisChart axisChart =
// creazione di uno stream per il file da salvare
FileOutputStream imageOutputStream = new FileOutputStream(
filename );
// salvataggio dell'immagine
JPEGEncoder13.encode(axisChart, 1.0f, imageOutputStream);
Qualsiasi
sia la scelta JCharts fornisce le classi per poter fare
liberamente la propria scelta.
La creazione di statistiche è solo un esempio
di utilizzo di un tool grafico e certamente non è
un esempio si matematica statistica. Sicuramente è
possibile ottenere grafici più significativi
e di maggiore valore; tutto dipende dal tipo di dato
filtrato e salvato, ma soprattutto dalla logica impostata
per estrarre i dati dal database.
Tutti i casi di discussione/esempio sono presenti nel
file zip.
Conclusioni
Nel presente articolo è stato mostrato come usare
un filtro per ricavare dati utili per produrre dei grafici
statistici utilizzando tool grafici open source. Sicuramente
l'aspetto più rilevante è che il filtro
è assolutamente una struttura non invasiva per
il progetto già esistente; infatti l'aggiunta
o la rimozione dello stesso si traduce in una manipolazione
del file web.xml.
Bibliografia
[1] JCharts - http://jcharts.sourceforge.net/
[2] JFreeChart - http://www.jfree.org/jfreechart/index.html
[3] Jakarta Log4j - http://jakarta.apache.org/log4j/docs/index.html
[4] JavaWorld - http://www.javaworld.com/javaworld/jw-06-2001/jw-0622-filters.html
Massimo Ferrario si occupa
di design e sviluppo software in archittetture J2EE
3-tier. Si occupa in particolar modo di programmazione
Object Oriented in linguaggio Java. Massimo Ferrario
è raggiungibile all'indirizzo e-mail massimo_ferrario@yahoo.it
|