Home > J2EE Pattern, java > J2EE Patterns: Intercepting Filter

J2EE Patterns: Intercepting Filter

8 novembre 2013

Intercepting Filter

Questo pattern rientra tra i pattern J2EE di tipo Presentation

Contesto
Spesso nelle applicazioni si ha la necessità di intercettare e gestire sia la richiesta che la risposta, quindi il flusso di dati in ingresso ed in uscita che si viene a instaurare sia prima che dopo il processamento.
Molto spesso questa necessità nasce da motivi disparati quali: esigenze di logging, informazioni statistiche di accesso, verifica dei diritti di accesso, verifica dell’encoding utilizzato dal client, supporto al browser utilizzato dal client, necessità di cifrare o encodare i dati ecc.
Per gestire queste problematiche spesso si riscontra l’impiego di una serie di controlli sparsi in tutte le pagine coinvolte ma questa gestione comporta non solo una duplicazione del codice ma oltretutto rende molto fragile l’applicazione a causa di questo approccio del tipo copy-and-paste e per non bastare questo genera un forte accoppiamento tra il front-end ed il back-end.

Soluzione
Una modalità consolidata per gestire questa problematica è quella di centralizzare la gestione delle richieste e delle risposte al fine di definire delle politiche uniche e centralizzate.
L’utilizzo del Pattern Intercepting Filter consente di gestire la fase di pre o post processamento della richiesta.
La gestione si avvale di un Filter Manager che gestisce una catena di filtri ed in questo modo è in grado di aggiungere o rimuovere i filtri.
Nelle specifiche delle servlet i filtri possono essere configurati facilmente utilizzando il deployment descriptior che ci consente di mappare una URL ad una catena di filtri pertanto i servlet container ci mettono a disposizione delle librerie che consentono di gestire facilmente questa problematica con una soluzione “dichiarativa” già pronta all’uso e senza dover scrivere codice se non quello necessario alla realizzazione del nostro filtro.
Laddove avessimo esisgenze particolari oppure volessimo utilizzare questo pattern per una appplicazione standalone potremmo implementare questo pattern, come vedremo negli esempi.

Partecipanti e Struttura
Di seguito l’elenco degli attori che partecipano al pattern:

  • Client: colui che invia le richiesta al FilterManager.
  • FilterManager: colui che gestisce il processamento dei filtri. Esso crea la FilterChain nel corretto ordini ed inizializza il processo.
  • FilterChain: il FilterChain è una collezione ordinata di filtri indipendenti.
  • FilterOne, FilterTwo: rappresentano dei filtri che vengono inseriti nella FilterChain.
  • Target: il Target è la risorsa richiesta dal client

Vediamo come si presenta il pattern Intercepting Filter utilizzando il Class Diagram:

Intercepting Filter Class Diagram

Intercepting Filter Class Diagram

Vediamo come si presenta la sequenza delle invocazioni nel pattern Intercepting Filter utilizzando il Sequence Diagram:

Intercepting Filter Sequence Diagram

Intercepting Filter Sequence Diagram

Implementazione
Questo pattern ha trovato un forte utilizzo nell’ambito web, in particolare nella specifica delle servlet è presente in un capitolo che illustra il funzionamento dei filtri e che ricalca l’applicazione di questo pattern.
Ho pensato di fare alcuni esempi, in particolare:

  1. utilizzo dei filtri come previsto dalla specifica delle servlet
  2. implementazione del pattern per una applicazione web
  3. implementazione del pattern per una applicazione standalone

1. Utilizzo dei filtri come previsto dalla specifica delle servlet

Come detto precedentemente nella specifica delle servlet è presente un capitolo in cui è illustrato come utilizzare i filtri al fine di effettuare delle azioni prima e dopo il processamento della richiesta. Infatti il suo utilizzo è molto semplificato ed occorre effettuare due operazioni:

  1. creare il filtro o i filtri da impiegare per eseguire le proprie azioni
  2. mappare nel deployment descriptor (il file web.xml) il filtro con la servlet o l’URL

La prima operazione consiste nel creare il filtro o i filtri da impiegare, in questo caso occorre che la classe filtro che creeremo implementi l’interfaccia javax.servlet.Filter.
Immaginiamo di voler realizzare un filtro che effettui il logging, come di seguito:

package patterns.j2ee.interceptingFilter.servlet;

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

public class LoggingFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain)
            throws IOException, ServletException {

        System.out.println("pre filter");
        chain.doFilter(request, response);
        System.out.println("post filter");
    }

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void destroy() {
    }

}

Realizziamo una Servlet che ci serva solo come target, ai fini dell’esempio:

package patterns.j2ee.interceptingFilter.servlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Target extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().println("Hello Filter");
    }
}

La seconda operazione consiste nel mappare nel file web.xml il nostro filtro appena creato con la servlet “/Target” in modo tale che possa essere invocata prima e dopo il target.
Tale configurazione dovrà essere effettuata come di seguito:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <filter>
        <filter-name>LoggingFilter</filter-name>
        <filter-class>patterns.j2ee.interceptingFilter.servlet.LoggingFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>LoggingFilter</filter-name>
        <servlet-name>Target</servlet-name>
    </filter-mapping>
    <servlet>
        <servlet-name>Target</servlet-name>
        <servlet-class>patterns.j2ee.interceptingFilter.servlet.Target</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>Target</servlet-name>
        <url-pattern>/Target</url-pattern>
    </servlet-mapping>
</web-app>

Adesso se invochiamo la servlet “/Target” vedremo nel file di log le istruzioni di logging che abbiamo scritto nel filtro e questo vuol dire che siamo passati tramite esso.

2. Implementazione del pattern per una applicazione web
Come mostrato precedentemente in una applicazione web java enterprise i filtri sono già previsti dalle specifiche e sono implementati nei container pertanto non c’è alcuna necessità di reinventarsi la ruota. Questo secondo esempio mostra una semplificata configurazione per evidenziare le interazioni tra i partecipanti della struttura del pattern ma solo per scopi didattici ed per approfondire il pattern.
Ipotizziamo per questo esempio uno scenario simile al precedente, un utente che invoca una pagina Home e viene filtrato da una attività di logging, come mostrato di seguito:

home web sequence

Inoltre, per aggiungere un minimo di complessità, prevediamo un secondo filtro che viene attivito solo per alcune pagine di profilazione e che effettui un controllo di autenticazione che se non superato determina un messaggio di errore.
Vediamo in questo caso come si presenta il Class Digram in UML:

home web class

Il primo partecipante del pattern è il FilterManager che si occupa di creare la catena di filtri e definire il target. Quindi se l’utente invoca la pagina del Profilo, verrà sottoposto anceh al filtro del LoginFilter che si occuperà di verificare l’utenza.

package patterns.j2ee.interceptingFilter.web;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FilterManager extends HttpServlet {

    @Override
    public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        //Creazione della catena di filtri
        FilterChain chain = new FilterChain();

        //Creazione di default
        chain.addFilter(new LoggingFilter());
        chain.setTarget(new Home());

        //Gestione del target richiesto
        if (request.getPathInfo().equals("/Profilo")) {
            chain.addFilter(new LoginFilter());
            chain.setTarget(new Profilo());
        }

        chain.doFilter(request, response);

    }
}

La FilterChain è la catena di filtri che viene alimentata dal FilterManager e detiene la referenza al Target che verrà invocato al termine delle invocazioni dei filtri. Dopo l’esecuzione del Target, il FilterChain completerà l’invocazione a ritroso dei filtri per permettere di eseguire le azioni di post-filtering previste.

package patterns.j2ee.interceptingFilter.web;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class FilterChain {

    private List<Filter> filtri = new LinkedList<Filter>();
    private int counter = 0;
    private HttpServlet target;

    public void addFilter(Filter filter) {
        filtri.add(filter);
    }

    public void setTarget(HttpServlet servlet) {
        target = servlet;
    }

    public void doFilter(
            HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException,
            IOException {
        if (counter < filtri.size()) {
            Filter filtro = filtri.get(counter);
            counter++;
            filtro.doFilter(request, response, this);
        } else {
            target.service(request, response);
        }
    }
}

Definiamo l’interfaccia Filter che definisce il metodo che i filtri dovranno implementare

package patterns.j2ee.interceptingFilter.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface Filter {

    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain);
}

Creaiamo il primo filtro, il LoggingFilter, che si occuperà di eseguire una semplice attività di logging.

package patterns.j2ee.interceptingFilter.web;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoggingFilter implements Filter {

    public void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {
        System.out.println("PRE LoggingFilter - Target: " + request.getRequestURI());
        try {
            chain.doFilter(request, response);
        } catch (Exception ex) {
        }
        System.out.println("POST LoggingFilter - Target: " + request.getRequestURI());
    }

}

Creiamo la nostra Servlet che ha solo scopo dimostrativo al fine di essere invocata al termine dei filtri.

package patterns.j2ee.interceptingFilter.web;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Home extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().append("Pagina Home");
    }
}

Adesso creiamo il filtro di login, la LoginFilter, che si occuperà di verificare che l’utenza in corso coincida con quelle previste, ovviamente è solo dimostrativo e sarebbe fuori contesto approfondire la casistica.

package patterns.j2ee.interceptingFilter.web;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginFilter implements Filter {

    //Semplificazione utenza autorizzata
    private List utentiAutorizzati = new ArrayList();
    {
        utentiAutorizzati.add("pippo");
        utentiAutorizzati.add("ciccio");
    }

    public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain chain) {
        if (!utentiAutorizzati.contains(req.getParameter("username")) ){
            System.out.println("PRE LoginFilter - Utente non autorizzato: " + req.getParameter("username"));
            try {
                res.getWriter().append("Pagina Errore: Utente non autorizzato!");
            } catch (IOException ex) {}
            return;
        }
        else{
            System.out.println("PRE LoginFilter - Utente autorizzato: " + req.getParameter("username"));
        }

        try {
            chain.doFilter(req, res);
        } catch (Exception ex) {}

        System.out.println("POST LoginFilter");
    }

}

Creiamo la servlet target che verrà invocata solo nel caso in cui il controllo del filtro avrà avuto esito positivo.

package patterns.j2ee.interceptingFilter.web;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class Profilo extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        response.getWriter().append("Pagina PROFILO: Buongiono " + request.getParameter("username"));
    }
}

3. Implementazione del pattern per una applicazione standalone
Come ultimo esempio ipotizziamo l’utilizzo di questo pattern per una applicazione standalone. In questo caso implementiamo il pattern alla stregua dell’esercizio precedente.
A fronte di una invocazione generica si viene rediretti alla pagina Home passando per il filtro di logging.
Vediamo di seguito il Sequence Diagram che mostra le interazioni esistenti tra i partecipanti

Sequence Intercepting Filter

Sequence Intercepting Filter

Mentre nel Class Diagram vediamo la struttura e le relazioni tra di essi

Simple Class Intercepting Filter

Simple Class Intercepting Filter

Il nostro punto di partenza è rappresentato dal FilterManager che dirige i lavori. In questo caso, a fronte di una invocazione, il client verrà rediretto verso una catena di filtri composta solo dal LoggingFilter per poi atterrare sul target rappresentato dalla classe Home.

package patterns.j2ee.interceptingFilter.standalone;

class FilterManager {

    public Response execute(Request request) {

        //Creazione della catena di filtri
        FilterChain chain = new FilterChain();

        //Creazione di default
        chain.addFilter(new LoggingFilter());
        chain.setTarget(new Home());

        Response response = new Response();
        chain.doFilterChain(request, response);
        return response;
    }
}

Il FilterChain svolge un ruolo importante in quanto non solo detiene la catena di filtri ma interagisce con essi sia prima che dopo l’invocazione del target.

package patterns.j2ee.interceptingFilter.standalone;

import java.util.LinkedList;
import java.util.List;

public class FilterChain {

    private List<Filter> filtri = new LinkedList<Filter>();
    private int counter = 0;
    private Target target;

    public void addFilter(Filter filter) {
        filtri.add(filter);
    }

    public void setTarget(Target target) {
        this.target = target;
    }

    public void doFilterChain(Request request, Response response) {
        if (counter < filtri.size()) {
            Filter filtro = filtri.get(counter);
            counter++;
            filtro.doFilter(request, response, this);
        }
        else {
            target.execute(request, response);
        }
    }

}

Definziamo l’interfaccia Filter che espone solo il metodo da essere implementato dalla classi figlie

package patterns.j2ee.interceptingFilter.standalone;

interface Filter {

    void doFilter(Request req, Response res, FilterChain chain);

}

Il LoggingFilter ha il semplice compito di loggare l’operazione sia prima che dopo l’esecuzione del target Home.

package patterns.j2ee.interceptingFilter.standalone;

class LoggingFilter implements Filter{

    public void doFilter(Request request, Response response, FilterChain chain) {
        System.out.println("PRE LoggingFilter - Target: " + request.getTarget());

        chain.doFilterChain(request, response);

        System.out.println("POST LoggingFilter - Target: " + request.getTarget());
    }

}

Definiamo l’interfaccia del Target in modo tale poter usare il polimorfismo per settare i target di interesse.

package patterns.j2ee.interceptingFilter.standalone;

public interface Target {

    public void execute(Request request, Response response);

}

Adesso implementiamo la classe Home che in questo semplice esempio mostra un semplice messaggio dimostrativo

package patterns.j2ee.interceptingFilter.standalone;

public class Home implements Target {

    public void execute(Request request, Response response) {
        response.addOutput("Pagina HOME");
    }

}

Il Client, che potrebbe essere un FrontController o un Facade o un Proxy ecc, invoca il FilterManager passando l’oggetto della richiesta ed ottenendo una risposta.

package patterns.j2ee.interceptingFilter.standalone;

public class Client {

    public static void main(String[] args) {
        FilterManager fm = new FilterManager();

        //Pagina non protetta + utenza anonima
        Response res = fm.execute(new Request("home", null ));
        System.out.println(res.getOutput());

    }

}

Vediamo come si presenta la richiesta che richiede la definizione del target e dell’utenza autenticata.

package patterns.j2ee.interceptingFilter.standalone;

public class Request {
    private final String target;
    private final String username;

    public Request(String target, String username) {
        this.target = target;
        this.username = username;
    }

    public String getTarget(){
        return target;
    }

    public String getUsername(){
        return username;
    }
}

La risposta è costituita dal contenuto che dovrà essere visualizzato all’utente.

package patterns.j2ee.interceptingFilter.standalone;

public class Response {

    private String output="";

    public String getOutput(){
        return output;
    }

    public void addOutput(String output ){
        this.output += output;
    }
}

Eseguendo l’esempio avremo questo output:

$JAVA_HOME/bin/java patterns.j2ee.interceptingFilter.standalone.Client
PRE LoggingFilter - Target: home
POST LoggingFilter - Target: home
Pagina HOME

Adesso introduciamo anche il filtro della Login per verificare che l’utenze è autenticata

Class Intercepting Filter

Class Intercepting Filter

Nel FilterManager prevediamo che nel caso in cui la richiesta è relativa alla pagina di profilo allora verrà aggiunta alla catena di filtro anche il filtro LoginFilter che effettuerà un controllo

package patterns.j2ee.interceptingFilter.standalone;

class FilterManager {

    public Response execute(Request request) {

        //Creazione della catena di filtri
        FilterChain chain = new FilterChain();

        //Creazione di default
        chain.addFilter(new LoggingFilter());
        chain.setTarget(new Home());

        //Gestione del target richiesto
        if (request.getTarget().equals("profilo")) {
            chain.addFilter(new LoginFilter());
            chain.setTarget(new Profilo());
        }

        Response response = new Response();
        chain.doFilterChain(request, response);
        return response;
    }
}

Il LoginFilter si occupa di controllare che l’utenza sia censita, effettua una autenticazione fake/dimostrativa, nel caso negativo allora blocco la catena di filtri mentre nel caso positivo allora continuo il processamento.

package patterns.j2ee.interceptingFilter.standalone;

import java.util.ArrayList;
import java.util.List;

class LoginFilter implements Filter {

    //Semplificazione utenza autorizzata
    private List utentiAutorizzati = new ArrayList();
    {
        utentiAutorizzati.add("pippo");
        utentiAutorizzati.add("ciccio");
    }

    public void doFilter(Request request, Response response, FilterChain chain) {
        if (!utentiAutorizzati.contains(request.getUsername()) ){
            System.out.println("PRE LoginFilter - Utente non autorizzato: " + request.getUsername());
            response.addOutput("Pagina Errore: Utente non autorizzato!");
            return;
        }
        else{
            System.out.println("PRE LoginFilter - Utente autorizzato: " + request.getUsername());
        }

        chain.doFilterChain(request, response);

        System.out.println("POST LoginFilter");
    }

}

Realizzo la nuova classe target rappresentata da Profilo che visualizza un messaggio di benvenuto

package patterns.j2ee.interceptingFilter.standalone;

class Profilo implements Target {

    public void execute(Request request, Response response) {
        response.addOutput("Pagina PROFILO: Buongiono " + request.getUsername());
    }

}

Il Client dovrà semplicemente invocare il nuovo target “profilo” per azionare il meccanismo di filtro.
In questo caso effettuo le tre invocazioni per visualizzare le casistiche previste.

package patterns.j2ee.interceptingFilter.standalone;

public class Client {

    public static void main(String[] args) {
        FilterManager fm = new FilterManager();

        //Pagina non protetta + utenza anonima
        Response res = fm.execute(new Request("home", null ));
        System.out.println(res.getOutput() + "\n");

        //Pagina protetta + utenza autorizzata
        res = fm.execute(new Request("profilo", "pippo"));
        System.out.println(res.getOutput() + "\n");

        //Pagina protetta + utenza anonima
        res = fm.execute(new Request("profilo", null));
        System.out.println(res.getOutput());

    }

}

L’esecuzione del codice produce questo output

$JAVA_HOME/bin/java patterns.j2ee.interceptingFilter.standalone.Client
PRE LoggingFilter - Target: home
POST LoggingFilter - Target: home
Pagina HOME

PRE LoggingFilter - Target: anagrafica
PRE LoginFilter - Utente autorizzato: pippo
POST LoginFilter
POST LoggingFilter - Target: anagrafica
Pagina PROFILO: Buongiono pippo

PRE LoggingFilter - Target: anagrafica
PRE LoginFilter - Utente non autorizzato: null
POST LoggingFilter - Target: anagrafica
Pagina Errore: Utente non autorizzato!
Categorie:J2EE Pattern, java
  1. 25 aprile 2014 alle 9:58 AM

    Anche se il commento specifica che il test andrebbe fatto ipotizzando che la richiesta arrivi da una pagina denominata “profilo”, forse l’ultima parte dell’esempio andrebbe modificato per renderlo più coerente e comprensibile a noi utenti autodidatti in :

    if (request.getTarget().equals(“anagrafica”)) {
    chain.addFilter(new LoginFilter());
    chain.setTarget(new Profilo());

    • 25 aprile 2014 alle 8:23 PM

      Si, sono daccordo con te, è stato un mio errore che correggerò. Grazie mille.

  1. No trackbacks yet.
I commenti sono chiusi.
%d blogger cliccano Mi Piace per questo: