MokaByte 75- Giugno 2003 
Filtri e grafici statistici
di
Massimo Ferrario
I filtri, introdotti in Servlet Api 2.3, consentono di fare operazioni di pre-processing di una richiesta e/o di post-processing di una risposta. E' possibile quindi raccogliere informazioni per creare delle statistiche visualizzabili, in formato grafico, con tool open source.

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:

  1. creare l'immagine e spedirla direttamente al client
  2. 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

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