Service-level requirements: soddisfare i requisiti non funzionali di una architettura software

L’architetto software è la figura professionale che ha l’obiettivo fondamentale di progettare l’architettura di un sistema software nel rispetto dei requisiti non funzionali (NFR)Di seguito, riporto un elenco generale di accorgimenti o best practices che occorre, o conviene, seguire per rispettare i requisiti di base quali performance, availability, reliability, extensibility, manageability, maintainability security.

Il seguente elenco è stato estratto da una lista di quesiti utili alla certificazione di JEE Architect (Oracle) che sto conseguendo; esso non è esaustivo e copre casi piuttosto generali. 

 

Come si può soddisfare la scalabilità del sistema (scalability)?

  • Creando una architettura ridondante, con l’applicazione di tecniche di clustering. I tier dovrebbero essere ridondanti: web tier, application tier, database, LDAP e load balancer, componenti di rete, ecc;
  • Load balancing: è la tecnica che permette di redirigere una richiesta ad uno dei tanti server disponibili in base ad un predeterminato algoritmo di load balancing. Il vantaggio è redistribuire il carico su differenti macchine, con prestazioni non eccessive, invece di avere una singola macchina ad alte prestazioni che gestisca tutte le richieste. In questo modo si risparmia sui costi e si ottimizza l’uso delle risorse computazionali. Per implementare il load balancing occorre scegliere l’appropriato algoritmo in base alle performance e alla disponibilità richieste. Un tipico algoritmo è il round-robin;
  • Riducendo il traffico di rete usando pattern, come DTO e Session Façade, ad esempio;
  • Valutando se scalare verticalmente o orizzontalmente (vertical scaling horizontal scaling). Per scalare un sistema occorre inserire hardware aggiuntivo: scalare verticalmente significa aggiungere risorse addizionali alla macchina, come processori, memoria o dischi; invece, scalare orizzontalmente significa aggiungere altre macchine all’architettura esistente, aumentando la capacità complessiva di sistema. La prima soluzione è più semplice, poiché non ha impatto sull’architettura: infatti, per scalare orizzontalmente l’architettura deve essere predisposta per supportare una cluster-based configuration e progettata in modo che i suoi componenti non dipendano dalla macchina su cui vengono eseguiti. La scelta, comunque, dipende dal costo dell’hardware richiesto per raggiungere i requisiti di performance e di scalabilità fissati col cliente;
  • Disegnando l’architettura in maniera modulare (modularity): conviene separare il sistema in componenti auto-consistente per poter scalare singolarmente le sue parti (web, business, integration tier, ad esempio);
  • Stateless architecture. Conviene utilizzare EJB di tipo stateless: l’utilizzo di stateful session bean comporta una scelta progettuale “povera” dal punto di vista della scalabilità. Inoltre, lo stato conversazionale dovrebbe essere mantenuto lato web (con l’uso di HttpSessionURL rewriting)
  • Usando un connection pooling per le connessioni al database. Inoltre, conviene minimizzare l’utilizzo di long transaction (in generale, l’uso delle transazioni dovrebbe avvenire solo se effettivamente necessario)

 

Come si possono ottimizzare le performance di un sistema?

  •  Attraverso la ridondanza (redundancy). La capacità del sistema può crescere attraverso il vertical scaling e solitamente viene definita height. La ridondanza è la dimensione che viene calcolata in base al numero di istanze configurate per svolgere in modo sincronizzato lo stesso lavoro (ad esempio, attraverso il load balancing): tale dimensione cresce quando si scala orizzontalmente e viene definita width. La ridondanza può migliorare le performance del sistema, oltre all’availability, alla reliability, alla scalability e alla extensibility, ma può comportare un peggioramento della security e della manageability.
  • Introducendo un meccanismo di caching dei dati, in modo da ridurre l’overhead computazionale e limitando il numero di richieste concorrenti. Si potrebbe introdurre un timeout sulle operazioni long-lasting, specialmente quelle che riguardano l’accesso a servizi esterni. In questo modo si migliora il system throughput.
  • Il caching si può introdurre anche grazie all’utilizzo di pattern, come Service Locator e Value List Handler, oppure Session Facade e DTO, che permettono di ridurre il traffico di rete, permettono di esporre servizi “a grana grossa” (course-grained services) che consentono di ridurre il round-trip time all’atto della chiamata ai servizi di business;
  • L’utilizzo delle transazioni deve essere effettuato solo se espressamente richiesto. In generale, peggiorano le performance complessive del sistema, ma ovviamente non si può evitare di utilizzarle, essendo necessarie per garantire la consistenza e l’integrità dei dati. Vanno valutati i singoli casi e fatte delle scelte, trovando il giusto trade-off;
  • Session management. Le specifiche Servlet consigliano di spostare la gestione della sessione lato web, attraverso l’uso della classe HttpSession. La memorizzazione degli oggetti in sessione deve essere ragionata: salvare oggetti onerosi, come collezioni di risultati di ricerca, è un conosciuto problema denominato “performance and scalability anti-pattern”. Conviene ridurre, dunque, i dati in sessione ed effettuare aggiornamenti “a grana grossa” (course grained update).
  • L’utilizzo di un algoritmo di load balancing, come Sticky sessionconsente di avere effetti benefici sulle performance e consente di evitare di avere frequenti sincronizzazioni di dati in sessione. Lo svantaggio è sulla availability: l’utente perde la sessione, ma il failover è più veloce. L’utilizzo di un elastic load balancing e di uno short duration time di sessione avita di avere una perdita di sessione.
  • Utilizzando Java Persistence API (JPA) in modalità lazy, in modo da evitare di caricare dati inutili in modo massivo quando si effettuano le query sui dati;
  • La replicazione passiva (passive replication) consente di avere un sistema poco sovraccarico e di evitare frequenti sincronizzazioni tra i nodi replicati.

 

Come si può soddisfare il requisito della disponibilità del sistema (availability)?

  • Realizzando una architettura ridondante; tutti i tier dovrebbero essere ridondanti (web tier, application tier, database, load balancer, componenti di rete, ecc.). Prevedendo ridondanzafailoverun componente singolo può essere indisponibile e avere un impatto negativo sulla reliability, ma il servizio continua ad essere disponibile grazie alla ridondanza;
  • Dunque, conviene prevedere un clustering con failover usando un hot stand-by server (con capacità extra), ossia un componente secondario che si attiva solo in caso di necessità (soluzione che comporta una spesa aggiuntiva per un componente che potrebbe non essere utilizzato se non in caso di inattività dei componenti principali – occorre valutare se conviene sostenere tale costo, con la garanzia di avere una architettura disponibile in caso di fallimento);
  • Prevedendo una configurazione in clustering e scegliendo un cluster software che permetta di amministrare il server group, rilevando softwarehardware failure e di gestire il failover, restartando i servizi in caso di fallimento. La configurazione two-node clusters (symmetric and asymmetric) è quella più comune: quella simmetrica prevede che entrambi i cluster siano attivi nello stesso istante, invece, quella asimmetrica prevede la presenza di un stand-by failover server.
  • Prevedendo una replicazione passiva (passive replication), dove lo stato dei componenti secondari viene sincronizzato con quello del componente principale, in caso di fallimento. Per il database, si potrebbe optare per una replicazione attiva (active replication), dove i componenti cluster sono tutti principali e si sincronizzano per gestire il carico (in questo modo non si ha una capacità extra inattiva in condizioni normali di funzionamento). Tuttavia, la replicazione attiva è onerosa poiché la sincronizzazione e il coordinamento tra i componenti aggiungono un certo overhead e complessità al sistema;
  • Attraverso la definizione di un piano di Disaster Recovery e di Service-Level Agreements (SLA) per i sistemi esterni da integrare. La definizione delle SLA deve essere concordata con il cliente e fissata in modo chiaro anche per il sistema che si intende progettare.


Come si garantisce il requisito di sicurezza del sistema (security)?

  • Con la ridondanza delle componenti di sistema, diminuisce anche la sicurezza. Questo perché potenzialmente si hanno più punti di vulnerabilità nel sistema;
  • Application security: la sicurezza applicativa si potrebbe soddisfare attraverso il transport level security (come SSL/TLS EAS 256 bit) o attraverso un message level security (come ad esempio, WS-Security). Inoltre, a livello applicativo occorrerebbe prevedere sempre una input validation, filtrando gli input inseriti dagli utenti sulle interfacce web, risolvendo ad esempio il problema del cross site scripting, e prevedendo un packet filtering firewall per evitare attacchi DDoS;
  • Si potrebbe prevedere una zona demilitarizzata (DMZ) per il web e per il business tier, in modo da proteggersi da attacchi esterni, usando il protocollo HTTPS, web server operanti come reverse proxy, una business DMZ accessibile solo attraverso web server “trusted”. Anche al database si dovrebbe accedere soltanto attraverso application server “trusted”.
  • Inoltre, applicando il principle of least privilege, secondo il quale si dovrebbe accedere solo alle informazioni e alle risorse necessarie. Tale principio prevede che utenti, amministratori, programmi, sistemi, ecc. dovrebbero possedere solamente i privilegi necessari per eseguire uno specifico task. Il principio dei privilegi minimi è un importante principio per limitare l’esposizione agli attacchi e per minimizzare i danni;  
  • Per l’integrazione di servizi esterni occorrerebbe utilizzare protocolli come SSL. Ad esempio, per l’accesso a Web Services si potrebbe utilizzare JAX-WS over SSL, in modo da sfruttare il message-level data integrity, grazie all’XML Signature, e il message-level and transport confidentiality, utilizzando l’encryption (WS-Security Specification)
  • Creando una architettura che separi i componenti funzionali o prevedendo delle security zone per ciascun componente: se un componente viene violato, viene compromesso solo tale componente e non tutti gli altri presenti nel sistema (punti di isolamento);
  • Con l’utilizzo di filtri (filter), componenti server-side ospitati dal web container, che permettono di intercettare le richieste in entrata prima che vengano ricevute dai rispettivi componenti target. I filtri sono utili per pre-processare le richieste, permettendo, ad esempio, di loggare eventi o validare le richieste (security checks);
  • Sfruttando la sicurezza messa a disposizione come servizio da un EJB container, facilmente configurabile anche a run-time;
  • Prevedendo un meccanismo di authorization (soltanto un utente valido e autenticato ha gli appropriati diritti per accedere ai dati e alle funzionalità di sistema), confidentiality (assicurando che i dati e le funzionalità di sistema siano protetti da accesso non autorizzati), integrity (assicurando che i dati di sistema non siano modificati o che ci siano interferenze da parte di componenti terzi, malintenzionati e non) e authentication (assicurando che l’identità di un utente che accede al sistema sia valida e corretta e che non possa essere impersonificata o compromessa in alcun modo). Per garantire tali requisiti, si potrebbe sfruttare Java Authentication and Authorization (JAAS);
  • Prevedendo un declarative security model, dove le regole di autorizzazione sono definite attraverso un file XML (deployment descriptor). Nel caso in cui occorra gestire una sicurezza “role-based” particolare, è possibile definire un programmatic security model.

 

Come si garantisce l’affidabilità (reliability) di un sistema?

  • La reliability assicura l’integrità e la consistenza dell’applicazione e di tutte le sue transazioni. Inoltre, definisce l’abilità di un sistema o di un componente di funzionare sotto determinate condizioni per un periodo di tempo specificato. E’ possibile garantire l’affidabilità del sistema utilizzando la ridondanza (redundancy): quando un server non risponde, altri possono lavorare al suo posto;
  • Utilizzando load balancing, failover e clustering;
  • Lo sticky session ha effetti negativi sulla reliability perché l’utente perde la sessione. Per ridurre tale effetto occorre prevedere uno short time session e un elastic load balancing
  • Anche la replicazione passiva ha un effetto negativo sulla reliability, perché ci potrebbe essere un tempo di indisponibilità del sistema (down time) in caso di fallimento e conseguente attivazione dello stand-by server.

 

Come si garantisce l’estensibilità del sistema (extensibility)?

  • Avere un sistema flessibile significa che l’aggiunta di funzionalità o la modifica di quelle esistenti non hanno impatto sull’architettura del sistema. Tale misura dipende da come il sistema è stato progettato e da quanto precisi sono stati i service-level requirements stabiliti con il cliente. I principi base che occorre coprire per garantire la flessibilità del sistema sono: low coupling, interfaces encapsulation.
  • L’applicazione deve essere progettata rispettando il separation of concernsconviene prevedere differenti layer (presentation, business e data access layer). Ogni layer è debolmente accoppiato con gli altri (loose coupling), grazie all’utilizzo di pattern, interfacce, ereditarietà, incapsulamento e best practices della programmazione ad oggetti;
  • Utilizzando pattern per separare l’applicativo in concern e layer: ad esempio, utilizzando DAO, Value List Handler, Front Controller, MVC, Service to Worker;
  • Progettando un object model di alta qualità: basta applicare i principi della programmazione ad oggetti e i design pattern (ad esempio, l’MVC pattern disaccoppia i componenti di user interface dai componenti di business logic);
  • Definendo un service level agreement (SLA) con il cliente e cercando di anticipare le modifiche applicative; attraverso la definizione di SLA è possibile definire uno scope di progetto chiaro e preciso e anticipare inaspettate modifiche al sistema. Occorrerebbe identificare le possibili “changed area” del sistema (ad esempio, la tecnologia di user interface utilizzata) e isolare tali aree in componenti coerenti: in questo modo, si può prevenire che la propagazione degli effetti dei cambiamenti abbia sensibili ripercussioni in tutto il sistema;
  • La ridondanza (redundancy) e la modularità (modularity) consentono di migliorare anche l’estensibilità del sistema;

 

Come si garantisce la manageability del sistema?

  • La manageability è l’abilità di gestire il sistema per assicura di averlo in salute, grazie al monitoraggio dei QoS requirements (scalability, reliability, availability, performance, security). Inoltre, un’alta manageability consente di cambiare la configurazione del sistema per migliorare la QoS dinamicamente senza impattare l’architettura del sistema stesso;
  • La ridondanza ha effetti negativi sulla manageability e non solo sulla sicurezza;
  • Sticky session ha effetti negativi sulla manageability;
  • Utilizzando Java Management Extensions (JMX) e prevedendo differenti livelli di logging per monitorare condizioni di errore, fatali o di warning durante il funzionamento dell’applicativo;
  • Scegliendo un software idoneo di load balancer per il cluster management.

 

Come si garantisce la maintainability del sistema?

  • La maintainability è la capacità di correggere difetti nelle funzionalità esistenti senza impatti su altri componenti del sistema. Tale misura non si valuta a tempo di deploy, ma quando si progetta l’architettura e viene garantita se si rispettano i principi di low coupling, modularity e documentation. Grazie alla modularity, ogni layer è debolmente accoppiato agli altri (loosely coupling), e sfruttando i principi di separation of concerns, i design pattern, le interfacce e le best practices della programmazione ad oggetti, è possibile fare in modo che il sistema sia più manutenibile;
  • Grazie ad una buona documentazione dell’applicativo (architecture diagrams, Interface agreements con i sistemi esterni, class diagrams, sequence diagrams, Java doc, ecc.);
  • Utilizzando lo Standard JEE Stack, con tecnologie come JSF, EJB, JPA, JAAS, JTA in modo da manutenere una soluzione standard e non “specific vendor”. Ad esempio, i componenti JSF si possono riutilizzare e la stessa tecnologia JSF è definita “toolable”, grazie al supporto di IDE maturi come Eclipse, Netbeans e IntelliJ.

[PrimeFaces] Due approcci per customizzare i tag di PrimeFaces

Logo Prime FacesIn un recente post, ho “lodato” PrimeFaces come uno dei migliori framework Java Server Faces (JSF) attualmente in circolazione. In effetti, le componenti grafiche, rispetto ad altre librerie del genere (vedi Rich Faces), sono sicuramente più stabili, complete e graficamente più carine. Tuttavia, può capitare di dover fare delle customizzazioni, specie per renderne alcune davvero accessibili (nonostante da documentazione si dichiari l’aderenza di PrimeFaces alle specifiche Accessibile Rich Internet Applications – WAI-ARIA).

Illustro qui due metodi per customizzare i tag di Prime. Nell’esempio, verrà modificato la componente “PanelGrid” (p:panelGrid), ma la procedura può essere applicata a tutti i tag della libreria “core”.

Entrambi metodi che vi illustrerò, prendono come riferimento la documentazione dei tag di PrimeFaces. Ecco il link alla documentazione dell’attuale versione (4.0): Documentazione PrimeFaces 4.0

Ad esempio, per il tag PanelGrid ecco le informazioni che ci servono:

tagPanelGrid PrimeFaces

 

1° Metodo: Customizzazione della Renderer Class (RenderKit)

Se vi occorre customizzare soltanto il rendering della componente grafica, basta estendere la classe “Renderer” ad essa associata. Come già anticipato sopra, per capire qual è tale classe, occorre leggere la documentazione relativa al tag da customizzare. Nel caso del panelGrid, la classe da estendere è org.primefaces.component.panelgrid.PanelGridRenderer

NOTA. Gli attributi rendererType e rendererClass, che si leggono da documentazione, non coincidono. Quindi, state attenti ad annotarvi queste informazioni, perché occorre configurarle allo stesso modo per la classe “custom” che scriverete, come illustrato di seguito.

Ecco la classe CustomPanelGridRenderer che estende quella “core” PanelGridRenderer:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

public class CustomPanelGridRenderer extends PanelGridRenderer{

	 public CustomPanelGridRenderer() {
                super();
	        System.out.println("costruttore classe custom renderer");
	 }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 
	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }
}

Nell’esempio precedente, è stato effettuato l’override del metodo encodeRow. In base al comportamento che vorrete customizzare, occorre individuare nella classe “Renderer” della vostra componente, il metodo associato a tale comportamento ed effettuarne l’override, modificandone la logica.

Per poter registrare la vostra classe “custom” di rendering, occorre configurare il faces.config.xml:

<?xml version='1.0' encoding='UTF-8'?>
<faces-config 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-facesconfig_2_0.xsd"
        version="2.0">

	<render-kit>
            <renderer>
                <component-family>org.primefaces.component</component-family>
                <renderer-type>org.primefaces.component.PanelGridRenderer</renderer-type>
                <renderer-class>it.francescoficetola.prime.web.component.CustomPanelGridRenderer</renderer-class>
            </renderer>
        </render-kit>

</faces-config>

I valori da associare agli attributi component-familyrenderer-type li prendete da documentazione del tag, come suddetto. In questo modo, ogni volta che utilizzate il “core tag” sulle pagine JSF della vostra applicazione (nell’esempio, p:panelGrid), in automatico verrà richiamata la vostra classe “renderer”.

NOTA. Se notate che nulla accade, provate ad inserire nel file web.xml della vostra applicazione le seguenti righe di codice (inserendo anche il file faces-config.xml nella cartella WEB-INF):

    <context-param>
        <param-name>javax.faces.CONFIG_FILES</param-name>
        <param-value>
            /WEB-INF/faces-config.xml, /faces-config.xml
        </param-value>
    </context-param>

 

2° Metodo: Customizzazione del tag (CustomTag)

Il secondo metodo che vi illustro è sicuramente il più completo, perché vi permette di modificare tutto il comportamento del tag associato alla componente grafica, e non solo il suo rendering.

In questo caso, vanno estesi sia il Component Class che il Renderer Class. Nel caso del p:panelGrid di questo esempio:

Renderer class: org.primefaces.component.panelgrid.PanelGridRenderer

Component class: org.primefaces.component.panelgrid.PanelGrid

package it.francescoficetola.prime.web.component;

import javax.faces.component.FacesComponent;
import org.primefaces.component.panelgrid.PanelGrid;

@FacesComponent("it.francescoficetola.prime.component.CustomPanelGrid")
public class CustomPanelGrid extends PanelGrid{

	public CustomPanelGrid() {
		super();
		System.out.println("Costruttore del component class");
	}

	@Override
	public void setColumns(int _columns) {
	    getStateHelper().put(PropertyKeys.columns, _columns);
	}
}

Nell’esempio del PanelGrid, è stato effettuato l’override del metodo setColumns, ma anche qui vale quanto detto nel 1° metodo di customizzazione, ossia che in base al comportamento che vorrete customizzare, occorre individuare nel Component Class il metodo associato per modificandone la logica, attraverso l’override.

Di seguito, vi ripeto la classe di “renderer” custom, che anche per questo metodo va prevista, ma che in più presenta anche delle annotazioni di classe:

package it.francescoficetola.prime.web.component;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.FacesRenderer;

import org.primefaces.component.column.Column;
import org.primefaces.component.panelgrid.PanelGrid;
import org.primefaces.component.panelgrid.PanelGridRenderer;
import org.primefaces.component.row.Row;

@FacesRenderer(
	    componentFamily=PanelGrid.COMPONENT_FAMILY,
	    rendererType="it.francescoficetola.prime.web.component.CustomPanelGridRenderer"
	)
public class CustomPanelGridRenderer extends PanelGridRenderer{

	public CustomPanelGridRenderer() {
	    System.out.println("costruttore della classe renderer");
	    }

         @Override
	 public void encodeRow(FacesContext context, Row row, String columnRole, String rowClass, String columnClass) throws IOException {
	        ResponseWriter writer = context.getResponseWriter();

	        writer.startElement("tr", null);
	        if(shouldWriteId(row)) {
	            writer.writeAttribute("id", row.getClientId(context), null);
	        }

	        writer.writeAttribute("class", rowClass, null);
	        writer.writeAttribute("role", "row", null);

	        for(UIComponent child : row.getChildren()) {
	            if(child instanceof Column && child.isRendered()) {
	                Column column = (Column) child;
	                String styleClass = null;
	                String userStyleClass = column.getStyleClass();

	                if(userStyleClass != null && columnClass != null) styleClass = columnClass + " " + userStyleClass;
	                else if(userStyleClass != null && columnClass == null) styleClass = userStyleClass;
	                else if(userStyleClass == null && columnClass != null) styleClass = columnClass;

	                writer.startElement("td", null);
	                if(shouldWriteId(column)) { 

	                    writer.writeAttribute("id", column.getClientId(context), null);
	                }
	                writer.writeAttribute("role", columnRole, null);

	                if(column.getStyle() != null) writer.writeAttribute("style", column.getStyle(), null);
	                if(styleClass != null) writer.writeAttribute("class", styleClass, null);
	                if(column.getColspan() > 1) writer.writeAttribute("colspan", column.getColspan(), null);
	                if(column.getRowspan() > 1) writer.writeAttribute("rowspan", column.getRowspan(), null);

	                column.encodeAll(context);

	                writer.endElement("td");
	            }
	        }

	        writer.endElement("tr");
	    }

}

A questo punto, occorre creare la taglib con i propri “custom tag”. Nel mio caso, ho creato un file customtag.taglib.xml, con il seguente contenuto:

<?xml version="1.0" encoding="UTF-8"?>
<facelet-taglib
    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-facelettaglibrary_2_0.xsd"
    version="2.0"
>
    <namespace>http://francescoficetola.it/ui</namespace>

    <tag>
        <tag-name>custompanelgrid</tag-name>
        <component>
            <component-type>it.francescoficetola.prime.web.component.CustomPanelGrid</component-type>
            <renderer-type>it.francescoficetola.prime.CustomPanelGridRenderer</renderer-type>
        </component>
    </tag>
</facelet-taglib>

Nel file della taglib, occorre registrare tutti i “custom tag” che prevedete (nel mio caso, ho per ora solo il custompanelgrid, che vi ricordo estende il tag “core” p:panelgrid di PrimeFaces). Salvate questo file nella directory WEB-INF del vostro progetto.

Infine, occorre registrare la taglib nel file web.xml, come segue:

	<!-- CONTEXT PARAM  -->
	<context-param>
	    <param-name>javax.faces.FACELETS_LIBRARIES</param-name>
	    <param-value>/WEB-INF/customtag.taglib.xml</param-value>
	</context-param>

Ora siamo pronti ad utilizzare il nostro custom tag. Ricordatevi di definire il namespace nella pagina JSF in cui utilizzare la vostra componente custom:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<ui:composition 
	xmlns="http://www.w3.org/1999/xhtml"
	xmlns:c="http://java.sun.com/jsp/jstl/core"
	xmlns:h="http://java.sun.com/jsf/html"
	xmlns:f="http://java.sun.com/jsf/core"
	xmlns:ui="http://java.sun.com/jsf/facelets"
	xmlns:p="http://primefaces.org/ui"
	xmlns:customtag="http://francescoficetola.it/ui"
>

<h:html>

   <h:head>
   </h:head>

   <h:body>

	<h:form id="myForm">

            <p:messages id="messages" /> 

	     <h:panelGroup id="myPanelGroup">

		 <customtag:custompanelgrid>

		        <p:row>
				<p:column>Colonna 1</p:column>
                                <p:column>Colonna 1</p:column>
			</p:row>

	         </customtag:custompanelgrid>

	     </h:panelGroup>

	</h:form>

   </h:body>

</h:html>

</ui:composition>

 

Riferimenti utili:

[JSF] PrimeFaces: una tecnologia di front-end che si sta imponendo nel mercato Enterprise

Logo Prime FacesMi è capitato di leggere un pò di articoli interessanti su PrimeFaces, tecnologia JSF che si sta imponendo in modo esponenziale nello scenario delle tecnologie web lato presentation. Ho utilizzato il suo antagonista Rich Faces, ma devo dire che Prime mi ha colpito particolarmente per tutta una serie di motivi che spiegherò in questo post.

In generale, i pregi delle tecnologie Java Server Faces (JSF) si possono riassumere nei seguenti punti:

  • rispetto del pattern architetturale Model-View-Controller (MVC), per separare la logica di presentazione dei dati da quella di business;
  • supporto alla realizzazione di una Rich Internet Application (RIA), in grado di offrire una user experience tipica di una Desktop application;
  • velocità nella presentazione dei dati e nella risposta alle interazioni utente, grazie al supporto asincrono AJAX di chiamata ai servizi e all’aggiornamento di porzioni di pagina (refresh parziale);
  • dinamicità ed interattività delle componenti grafiche, con possibilità di interazioni utente complesse, come, ad esempio, drag&drop, ridimensionamento e trascinamento;
  • facilità e velocità nello sviluppo dell’interfaccia utente, grazie al riuso di componenti già distribuite nel framework e alla semplicità nella loro customizzazione;
  • accessibilità delle pagine web generate e supporto alla presentazione su dispositivi mobile;
  • compatibilità con gli attuali web browser, ma anche supporto per quelli più datati.

Sulla base dei punti su riportati, ho provato il seguente set di tecnologie, tra loro compatibili, per lo sviluppo di un front-end di una Rich Internet Application:

  • Prime Faces: implementazione dello standard Java Server Faces – JSF 2.2;
  • JQuery/JQuery UI: framework javascript per la facile implementazione di componenti grafiche dinamiche ed interattive. Sono supportati nativamente da Prime Faces;
  • HTML 5: nuovo linguaggio di markup per l’implementazione di pagine web (in corso di standardizzazione);
  • CSS 3: evoluzione di CSS 2 per la definizione dello stile delle pagine web e della struttura dell’interfaccia utente.

Prime Faces è una implementazione dello standard “server-side” Java Server Faces (JSF 2.2). Fornisce un approccio “component-centric” per lo sviluppo di interfacce utente in tecnologia JEE, facilmente manutenibili, con modello di progettazione architetturale Model-View-Controller (MVC). Prime Faces permette di aggiungere funzionalità AJAX, sfruttandone le sue potenzialità come il meccanismo di chiamata asincrona a servizi, il refresh parziale delle pagine, le notifiche push, ecc. Esistono diverse implementazioni di JSF e le più diffuse sono le seguenti: Rich Faces, Ice Faces, MyFaces e Prime Faces. Quest’ultima sta riscontrando grande successo nella comunità di sviluppatori di front-end JEE, come si evidenzia dal seguente grafico di Google Trends:

Google Trend Prime Faces

Ecco i vantaggi che si traggono dall’utilizzo di Prime Faces:

  • Ricca suite di componenti. Prime Faces offre una suite open source di componenti UI avanzate (widget) per l’integrazione di funzionalità Ajax in applicazioni RIA (Rich Internet Application). Esistono, ad esempio, componenti per la realizzazione di tabelle con funzionalità avanzate (ordinamento, filtraggio, drag&drop, ecc.), menù ad albero, finestre a comparsa, calendar, picklist, widget per il file upload multiplo e tante altre (vedi Prime Faces – ShowCase);
  • Struttura multi-layout. Si può implementare in modo agevole una struttura multi-colonna con Prime Faces, utilizzando e customizzando i temi di cui dispone il framework e personalizzandone i fogli di stile, con l’utilizzo dei flexible box in CSS 3 (aree della pagina web con dimensioni flessibili).
  • Navigazione dinamica tra le pagine e finestre multiple con sessioni separate di lavoro. Grazie a JSF è possibile definire in modo semplice la navigazione tra le pagine, sia in modo predefinito che attraverso logiche di business. Inoltre, attraverso la definizione di opportuni “scope” si può rendere possibile la lavorazione su finestre multiple del browser, ciascuna con una sessione separata di lavoro.
  • Performance e supporto. Prime Faces è una libreria leggera e ciascuna componente in esso distribuito è piuttosto semplice da customizzare. Essendo una tecnologia piuttosto recente, è ottimizzata per il funzionamento sui browser di nuova generazione. Il grado di maturità delle componenti grafiche è alto, essendo il framework arrivato alla versione stabile 4.0. Inoltre, esistono una ricca documentazione e una attiva community online, per la risoluzione di comuni problematiche.
  • Compatibilità con i web browser recenti. Prime Faces garantisce la compatibilità con i browser più recenti, come Internet Explorer 9+, Google Chrome 17+  e Firefox 13+. Si dichiara il supporto anche per browser meno recenti, come Internet Explorer 7/8, sui quali viene rispettata l’interattività delle componenti (non di tutte però!), ma non il “look-and-feel” (per cui occorrerà agire manualmente modificando i CSS).
  • Notifiche push. Prime Faces consente di aggiungere funzionalità di push notification, utili per esempio per notificare cambiamenti di stato o aggiornamenti delle informazioni, in modo automatico ed asincrono, senza il refresh manuale delle pagine web.
  • Predisposizione al mobile. Grazie all’utilizzo di JQuery Mobile, è possibile realizzare, con l’estensione Prime Faces Mobile, pagine web con un “look-and-feel” simile alle app native (come quelle per iOs o Android)
  • Accessibilità e grafica responsive. Contrariamente ad altri framework AJAX-based, Prime Faces implementa le linee guida ARIA (Accessible Rich Internet Application) del W3C, garantendo per le componenti più comuni (tabelle, accordion, menù ad albero, componenti di input, …) la piena accessibilità. Inoltre, con l’ausilio di librerie Javascript (come BootStrap.js), è possibile realizzare con Prime Faces anche pagine in grafica “responsive”, ossia adattabile ai vari dispositivi (anche mobile).
  • Supporto a HTML 5. La maggior parte delle nuove componenti di Prime Faces sono sviluppate con HTML 5. Comunque, lo standard JSF 2.2 dichiara la possibilità di integrazione con tag HTML 5, utile per inserire nuove feature nelle pagine web, come i tag semantici.

 

Altri riferimenti utili:

 

[Spring] Spring3: dai Controller ai Repository [Parte2]

In questa Parte 2 del tutorial di Spring3, vengono mostrate alcune classi di esempio con relative annotazioni per ciascuno strato della nostra web application:

  • strato controller (Spring MVC): annotazione @Controller
  • strato di business logic: annotazione @Service
  • strato di persistenza e modellazione dei dati (Spring ORM): annotazioni @Entity e @Repository

Model @Entity

Riporto qui di seguito un esempio di model o entity, che mappa una tabella sul database con relative associazioni ad altre entità. Il model viene annotato con @Entity e deve essere enumerato nelle annotatedClass del file spring-orm.xml, visto nella Parte1 di questo tutorial. Per maggiori informazioni, leggere anche Hibernate Annotations.

Nell’esempio sono riportate anche delle annotazioni per la validazione dei campi del model (javax.validation e org.hibernate.validator.constraints), che spostano la validazione lato server verso il lato persistenza e che Spring intercetta e gestisce con opportuni messaggi di stato.

package com.yourDomain.model;

import com.yourDomain.interceptors.IAuditLog;

import java.io.Serializable;
import java.util.Date;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.Length;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.format.annotation.DateTimeFormat;

@Entity
@Table(name="utenti")
@NamedQueries({
	@NamedQuery(name = "Utente.findAll", query = "from Utente where id!=-1 order by cognome"),
	@NamedQuery(name = "Utente.findById", query = "from Utente r where r.id = :idUtente"),
	@NamedQuery(name = "Utente.findByCodiceFiscaleAndEmail", query = "from Utente r where lower(r.codiceFiscale) = lower(:codiceFiscale) and lower(r.email) = lower(:email) and dataCancellazione is null and abilitato = true"),
	@NamedQuery(name = "Utente.findByCodiceFiscaleOrEmail", query = "from Utente r where (lower(r.codiceFiscale) = lower(:codiceFiscale) or lower(r.email) = lower(:email) ) and dataCancellazione is null")
})
public class Utente implements Serializable,IAuditLog {

	private static final long serialVersionUID = 8797826874630455491L;

	public static final Integer ID_ADMINISTRATOR = -1;

	@Id
	@GeneratedValue
	@Column(name="id")
	private Integer id;

	@Column(name="matricola",nullable=true,length=50)
	private String matricola;

	@Column(name="cognome",nullable=false,length=255)
	@NotEmpty
	@NotNull
	private String cognome;

	@Column(name="nome",nullable=false,length=255)
	@NotEmpty
	@NotNull
	private String nome;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=StatoCivile.class)
	@JoinColumn(name = "fk_stato_civile", nullable = true, referencedColumnName = "id")
	private StatoCivile statoCivile;

	@Column(name="codice_fiscale",nullable=true,length=16)
	@NotEmpty
	@NotNull
	@Pattern(regexp="^[a-zA-Z]{6}[0-9]{2}[abcdehlmprstABCDEHLMPRST]{1}[0-9]{2}([a-zA-Z]{1}[0-9]{3})[a-zA-Z]{1}$")
	private String codiceFiscale;

	@Column(name="data_nascita",nullable=true)
	@Temporal(TemporalType.DATE)
	@DateTimeFormat(pattern="dd/MM/yyyy")
	private Date dataNascita;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=Comune.class)
	@JoinColumn(name = "fk_comune_nascita", nullable = true, referencedColumnName = "id")
	private Comune comuneNascita;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=Stato.class)
	@JoinColumn(name = "fk_stato_nascita", nullable = true, referencedColumnName = "id")
	private Stato statoNascita;

	@Column(name="indirizzo_residenza",length=255,nullable=true)
	private String indirizzoResidenza;

	@Column(name="cap_residenza",length=5,nullable=true)
	@Length(min=0,max=5)
	@Pattern(regexp="^[0-9]{0,5}")
	private String capResidenza;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=Comune.class)
	@JoinColumn(name = "fk_comune_residenza", nullable = true, referencedColumnName = "id")
	private Comune comuneResidenza;

	@Column(name="email",length=255,nullable=false)
	@NotEmpty
	@NotNull
	@Email
	private String email;

	@Column(name="partita_stipendio",length=50,nullable=true)
	private String partitaStipendio;

	@Column(name="sesso",length=1,nullable=true)
	@NotEmpty
	@NotNull
	private String sesso;

	@Column(name="telefono",length=100,nullable=true)
	//@Pattern(regexp="^(1\\s*[-\\/\\.]?)?(\\((\\d{3})\\)|(\\d{3}))\\s*[-\\/\\.]?\\s*(\\d{3})\\s*[-\\/\\.]?\\s*(\\\\d{4})\\s*(([xX]|[eE][xX][tT])\\.?\\s*(\\d+))*$")
	private String telefono;

	@Column(name="fax",length=100,nullable=true)
	//@Pattern(regexp="^(1\\s*[-\\/\\.]?)?(\\((\\d{3})\\)|(\\d{3}))\\s*[-\\/\\.]?\\s*(\\d{3})\\s*[-\\/\\.]?\\s*(\\\\d{4})\\s*(([xX]|[eE][xX][tT])\\.?\\s*(\\d+))*$")
	private String fax;

	@Column(name="cellulare",length=100,nullable=true)
	//@Pattern(regexp="^(1\\s*[-\\/\\.]?)?(\\((\\d{3})\\)|(\\d{3}))\\s*[-\\/\\.]?\\s*(\\d{3})\\s*[-\\/\\.]?\\s*(\\\\d{4})\\s*(([xX]|[eE][xX][tT])\\.?\\s*(\\d+))*$")
	private String cellulare;

	@Column(name="codice_abi",length=100,nullable=true)
	private String codiceABI;

	@Column(name="codice_cab",length=100,nullable=true)
	private String codiceCAB;

	@Column(name="numero_figli",nullable=true)
	private Integer numeroFigli;

	@Column(name="perc_invalidita",length=100,nullable=true)
	//@Pattern(regexp="^\\d+((\\.|,)\\d+)?$")
	private String percentualeInvalidita;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=AlboProfessionale.class)
	@JoinColumn(name = "fk_albo_professionale", nullable = true, referencedColumnName = "id")
	private AlboProfessionale alboProfessionale;

	@ManyToOne(fetch=FetchType.EAGER, targetEntity=SpecializzazioneProfessionale.class)
	@JoinColumn(name = "fk_spec_professionale", nullable = true, referencedColumnName = "id")
	private SpecializzazioneProfessionale specializzazioneProfessionale;

	@Column(name="data_modifica",nullable=true)
	@Temporal(TemporalType.DATE)
	@DateTimeFormat(pattern="dd/MM/yyyy")
	private Date dataModifica;

	@Column(name="data_creazione",nullable=true)
	@Temporal(TemporalType.DATE)
	@DateTimeFormat(pattern="dd/MM/yyyy")
	private Date dataCreazione;

	@Column(name="data_cancellazione",nullable=true)
	@Temporal(TemporalType.DATE)
	@DateTimeFormat(pattern="dd/MM/yyyy")
	private Date dataCancellazione;

	@Column(name="data_ultimo_accesso",nullable=true)
	@Temporal(TemporalType.DATE)
	@DateTimeFormat(pattern="dd/MM/yyyy")
	private Date dataUltimoAccesso;

	@ManyToOne(fetch=FetchType.LAZY, targetEntity=Utente.class)
	@JoinColumn(name = "fk_utente_modifica", nullable = true, referencedColumnName = "id")
	private Utente utenteModifica;

	@Column(name="abilitato",nullable=false)
	private boolean abilitato = true;

	@ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL,targetEntity=GruppoOperativo.class)
	@JoinTable(name = "associativa_utenti_gruppi",
			joinColumns = @JoinColumn(name = "fk_Utente", referencedColumnName = "id"),
			inverseJoinColumns = @JoinColumn(name = "fk_gruppo", referencedColumnName = "id")
	)
	private List<GruppoOperativo> gruppiOperativi;

	@ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.ALL,targetEntity=TitoloStudio.class)
	@JoinTable(name = "associativa_utenti_titolistudio",
			joinColumns = @JoinColumn(name = "fk_Utente", referencedColumnName = "id"),
			inverseJoinColumns = @JoinColumn(name = "fk_titolo_studio", referencedColumnName = "id")
	)
	private Set<TitoloStudio> titoliStudio;

	@OneToMany(mappedBy = "Utente", cascade = CascadeType.ALL, fetch = FetchType.LAZY, targetEntity=Rapporto.class)
	private List<Rapporto> rapporti;

	//@Column(name="nato_estero",nullable=true)
	@Transient
	private boolean natoInStatoEstero;

	@Transient
	private boolean isLogged;

	@Transient
	private String maxVisibility;

	public Utente(){

	}

/**
    QUI TUTTI I METODI DI GET E SET
**/

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((id == null) ? 0 : id.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Utente other = (Utente) obj;
		if (id == null) {
			if (other.id != null)
				return false;
		} else if (!id.equals(other.id))
			return false;
		return true;
	}

}

 

@Controller

Il controller è una interfaccia che riceve una richiesta HTTP – HttpServletRequest e HttpServletResponse – simile alla Action di Struts. Dunque, definisce lo strato di interfaccia tra presentation e business layer. L’annotazione da utilizzare è @Controller e per permettere a Spring MVC di effettuare l’autowiring in fase di startup della web app occorre inserire nel file di configurazione (spring-application.xml) il tag mvc:annotation-driven (come visto nella Parte1). Nel seguente esempio, vengono annotati anche gli editors, che sono utili per popolare dinamicamente select o checkbox lato template. L’annotazione @InitBinder serve per configurare il controller allo startup della web application (in questo caso, a registrare la lista degli Editors). Con @Autowired viene iniettato il servizio che verrà utilizzato dal controller.

Non è necessaria una interfaccia da implementare, ma occorre solo annotare la classe con @Controller, in quanto il controller stesso non viene “iniettato”.

Per maggiori dettagli sui controller e su Spring MVC, leggere questo link: Web MVC Framework.

package com.yourDomain.controller;

import com.yourDomain.model.Profilo;
import com.yourDomain.model.Rapporto;
import com.yourDomain.model.Utente;

import java.beans.PropertyEditorSupport;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.validation.Valid;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;

@Controller
@RequestMapping("/anagraficautente")
@SessionAttributes({"utenteLoggato","utenteSelezionato"})
public class UtenteController {

	protected static Logger log = Logger.getLogger(UtenteController.class);

	@Autowired
	UtenteService UtenteService;

	@InitBinder
	public void initBinder(WebDataBinder b) {
		DateFormat dateFormat = new SimpleDateFormat(SiapConfigProperties.getValue("dateFormat.small"));
		b.registerCustomEditor(Profilo.class, new ProfiloEditor());
	}

	//EDITOR
	private class DateEditor extends CustomDateEditor {

		public DateEditor(DateFormat dateFormat, boolean allowEmpty) {

			super(dateFormat, allowEmpty);
			// TODO Auto-generated constructor stub
			}
			@Override
			public void setAsText(String text) throws IllegalArgumentException {
				try {
					if(!Utils.isEmpty(text)){
						DateFormat df = new SimpleDateFormat(SiapConfigProperties.getValue("dateFormat.small"));
						setValue(df.parse(text));
					}
				} catch (Exception e) {
					setValue(null);
				}
		    }
		    @Override
		    public String getAsText() {
		    	if(((Date) getValue()) == null)
		    		return "";

		    	DateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
		        return ((String) formatter.format(getValue()));
		    }
	}

	//EDITOR
	private class ProfiloEditor extends PropertyEditorSupport {
			@Override
			public void setAsText(String text) throws IllegalArgumentException {
				try {
					if(!Utils.isEmpty(text)){
						Profilo profilo = profiloService.getProfilo(Integer.valueOf(text));
						if(profilo.getId()==null)setValue(null);
						else setValue(profilo);
					}
				} catch (Exception e) {
					setValue(null);
				}
		    }
		    @Override
		    public String getAsText() {
		    	if((Profilo) getValue() == null ||((Profilo) getValue()).getId()==null)
		    		return "";
		    	String id = ((Profilo) getValue()).getId().toString();
		        return id;
		    }
	}

	@RequestMapping(value = "/utente", method = {RequestMethod.GET,RequestMethod.POST})
	public ModelAndView redirectToAnagraficaUtente(@RequestParam(value = "id", required = false) String id,
			HttpSession session,
			ModelAndView mav) {

		try{
			Utente Utente = null;

			if(id == null && session.getAttribute("UtenteSelezionata") != null){
				Utente = (Utente)session.getAttribute("UtenteSelezionata");
			}else if(id != null){
				if(id != ""){
					Utente = UtenteService.getUtente(Integer.parseInt(id));
				}else if(id == ""){
					Utente = (Utente) session.getAttribute("utenteLoggato");
				}
				mav.addObject("UtenteSelezionata", Utente);
			}

			if(Utente == null){
				mav.addObject("result", ResultBean.writeMessageByKey(ResultBean.ERROR, "Utente.storico.error"));
				mav.setViewName(MappingMavController.STORICO_CARRIERA_JSP);

			}

			mav.setViewName("/anagraficaUtente");

		} catch (Exception e) {
			mav.addObject("result", ResultBean.writeMessageByKey(ResultBean.ERROR, "error.generic"));
			log.error("UtenteController - metodo redirectToAnagraficaUtente: ",e);
		}	

		return mav;
	}

}

 

@Service

Con l’annotazione @Service viene definita la classe di servizio con la logica di business, iniettata nel controller o in altri strati della nostra web application. Tale classe implementa una interfaccia. In tale strato è possibile definire la gestione delle transazioni, sia mappando le regole sul file spring-orm.xml, come visto nella Parte1, che inserendo sulla classe di servizio (gestione globale della transazione) e sui singoli metodi (gestione locale della transazione) l’annotazione @Transactional (definendo il tipo di propagazione, come avviene negli EJB). In tale classe vengono “iniettati” anche i DAO per la comunicazione con il database o altre classi di servizio (annotazione @Autowired).

package com.yourDomain.service;

import com.yourDomain.model.Utente;
import java.util.Map;

public interface UtenteService {
	Utente getUtenteByCodiceFiscaleAndEmail (String codiceFiscale, String email) throws Exception;
	boolean updateUtente (Utente Utente) throws Exception;
	public Map<String,String> getContestiAndOperazioniUtente (Utente Utente) throws Exception;
}


package it.beniculturali.siapweb.service.impl;

import it.beniculturali.siapweb.dao.ContestoDao;
import it.beniculturali.siapweb.dao.UtenteDao;
import it.beniculturali.siapweb.model.Contesto;
import it.beniculturali.siapweb.model.Utente;
import it.beniculturali.siapweb.service.UtenteService;
import it.beniculturali.siapweb.utilities.Utils;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("utenteService")
@Transactional(propagation=Propagation.REQUIRED, readOnly=true)
public class UtenteServiceImpl implements UtenteService {

	protected static Logger log = Logger.getLogger(UtenteServiceImpl.class);

	public UtenteServiceImpl(){

	}

	@Autowired
	private ContestoDao contestoDao;

	@Autowired
	private UtenteDao utenteDao;

	@Override
	/**
	 * metodo che ritorna la utente con un dato codice fiscale e una email
	 *
	 * @param codiceFiscale, email
	 * @return Utente
	 *
	 */
	public Utente getUtenteByCodiceFiscaleAndEmail (String codiceFiscale, String email) throws Exception{
		log.debug("UtenteServiceImpl - getUtenteByCodiceFiscaleAndEmail: inizio metodo");
		Utente utente = utenteDao.getUtenteByCodiceFiscaleAndEmail(codiceFiscale, email);
		log.debug("UtenteServiceImpl - getUtenteByCodiceFiscaleAndEmail: fine metodo");
		return utente;
	}

	/**
	 * metodo che aggiorna i dati di una determinata utente
	 *
	 * @param codiceFiscale, email
	 * @return Utente
	 *
	 */
	@Override
	@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
	public boolean updateUtente(Utente utente) throws Exception {
		log.debug("UtenteServiceImpl - updateUtente: inizio metodo");

			boolean result = false;
			log.debug("Aggiornamento dei dati della utente con id."+utente.getId());
			result = utenteDao.updateUtente(utente);

		log.debug("UtenteServiceImpl - updateUtente: fine metodo");
		return result;
	}

	/**
	 * metodo che ritorna la mappa dei contesti di un utente con relative operazioni permesse
	 *
	 * @param utente
	 * @return
	 * @throws Exception
	 */
	@Override
	public Map<String,String> getContestiAndOperazioniUtente(Utente utente) throws Exception {
		Map<String,String> contestiUtenteMap = new HashMap<String, String>();
		log.debug("UtenteServiceImpl - getContestiAndOperazioniUtente: inizio metodo");
		try{

			List<Contesto> contestiUtente = contestoDao.getContestiAndOperazioniUtente(utente);

			//costruisco la lista delle operazioni per ogni contesto

			if(contestiUtente!=null){

				for(Contesto contestoUtente : contestiUtente){

					//costruisco la mappa con le operazioni permesse dell'utente loggato
					contestiUtenteMap.put(contestoUtente.getId().toString(), Utils.convertListStringInSingleString(contestoUtente.getTipiOperazioniUtente(),","));
				}
			}

		}
		catch(Exception e){
			log.error("ContestoDaoImpl - getContestiAndOperazioniUtente: ",e);
		}
		log.debug("UtenteServiceImpl - getContestiAndOperazioniUtente: fine metodo");
		return contestiUtenteMap;
	}

}

 

@Repository

L’annotazione @Repository definisce le classi dello strato di persistenza, ossia i cosiddetti DAO (Data Access Object). Anche qui le classi implementano una interfaccia, visto che verranno “iniettate” nello strato di servizio (con annotazione @Autowired). Qui viene iniettato anche il SessionFactory (o l’HibernateTemplate), il manager che ci permetterà di eseguire le operazioni CRUD sulla basedati definita e configurata nella Parte1 di questo tutorial.

package com.yourDomain.dao;

import com.yourDomain.model.GruppoOperativo;
import com.yourDomain.model.Utente;

import java.util.List;

public interface UtenteDao {

	List<Utente> getUtenti() throws Exception;
	Utente getUtenteByCodiceFiscaleAndEmail (String codiceFiscale, String email) throws Exception;
	boolean isUtenteRegistrato (String codiceFiscale, String email, Integer idUtente) throws Exception;
	Utente getUtente (Integer idUtente) throws Exception;
	public int countUtentiForGruppoOperativo (GruppoOperativo gruppoOperativo) throws Exception;
	boolean saveUtente(Utente nuovoUtente) throws Exception;
	boolean updateUtente(Utente risorsa) throws Exception;

}

 

package com.yourDomain.dao.impl;

import com.yourDomain.dao.UtenteDao;
import com.yourDomain.model.GruppoOperativo;
import com.yourDomain.model.Utente;

import java.util.Date;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.FetchMode;
import org.hibernate.Hibernate;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Repository("utenteDao")
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class UtenteDaoImpl implements UtenteDao {

	@Autowired
	SessionFactory sessionFactory;

	protected static Logger log = Logger.getLogger(UtenteDaoImpl.class);

	public UtenteDaoImpl() {

	}

	/**
	 * metodo che ritorna la lista degli utenti
	 *
	 * @return List<Utente>
	 */
	@SuppressWarnings("unchecked")
	@Override
	public List<Utente> getUtenti() {
		Session session = sessionFactory.getCurrentSession();
		Query query = session.getNamedQuery("Utente.findAll");
		return query.list();
	}

	/**
	 * metodo che ritorna l'utente con un dato codice fiscale e una email
	 *
	 * @param codiceFiscale
	 *            , email
	 * @return Utente
	 *
	 */
	@Override
	public Utente getUtenteByCodiceFiscaleAndEmail (String codiceFiscale, String email) throws Exception{
		Session session = sessionFactory.getCurrentSession();
		Query query = session.getNamedQuery("Utente.findByCodiceFiscaleAndEmail")
			.setString("codiceFiscale", codiceFiscale)
			.setString("email", email);
		return (Utente)query.uniqueResult();
	}

	/**
	 * metodo che ci dice se c'è un utente registrato con un dato codice fiscale o email
	 *
	 * @param codiceFiscale
	 *            , email
	 * @return Utente
	 *
	 */
	@SuppressWarnings("unchecked")
	@Override
	public boolean isUtenteRegistrato (String codiceFiscale, String email, Integer idUtente) throws Exception{
		Session session = sessionFactory.getCurrentSession();
		Criteria criteria = session.createCriteria(Utente.class);
		criteria.add(Restrictions.isNull("dataCancellazione"));

		Criterion criterion1 = Restrictions.eq("codiceFiscale", codiceFiscale).ignoreCase();
		Criterion criterion2 = Restrictions.eq("email", email).ignoreCase();

		Criterion ORcriterion	= Restrictions.or(criterion1, criterion2);
		criteria.add(ORcriterion);

		if(idUtente!=null){

			criteria.add(Restrictions.ne("id",idUtente));
		}

		List<Utente> utentiRegistrati = criteria.list();

		return utentiRegistrati!=null&&utentiRegistrati.size()!=0?true:false;
	}

	/**
	 * metodo che ritorna un utente corrispondente ad un determinato id
	 *
	 * @param idUtente
	 *
	 */
	@Override
	public Utente getUtente(Integer idUtente) throws Exception {
		Session session = sessionFactory.getCurrentSession();
		Query query = session.getNamedQuery("Utente.findById")
			.setInteger("idUtente", idUtente);
		return (Utente)query.uniqueResult();
	}

	/**
	 *  metodo che conta quanti utenti sono associati a quel gruppo operativo
	 *
	 * @param gruppoOperativo
	 * @return
	 * @throws Exception
	 */
	@Override
	public int countUtentiForGruppoOperativo (GruppoOperativo gruppoOperativo) throws Exception{
		Session session = sessionFactory.getCurrentSession();
		Criteria criteria = session.createCriteria(Utente.class)
		.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
	    .setFetchMode("gruppiOperativi", FetchMode.JOIN);  

		criteria.createAlias("gruppiOperativi", "g",CriteriaSpecification.INNER_JOIN);
		criteria.add(Restrictions.eq("g.id", gruppoOperativo.getId()));
		criteria.add(Restrictions.isNull("dataCancellazione"));

		Number count = (Number) criteria.setProjection(Projections.rowCount()).uniqueResult();

		return count!=null?count.intValue():0;

	}

	/**
	 * metodo che aggiorna i dati di una determinata utente
	 *
	 * @param idUtente
	 *
	 */
	@Override
	@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
	public boolean updateUtente(Utente utente) throws Exception {

		try{

			Session session = sessionFactory.getCurrentSession();

			utente.setDataModifica(new Date());
		    session.clear();
			session.update(utente);
			session.flush();
			//session.evict(utente);
		}
		catch(Exception e){
			log.error("UtenteDaoImpl - updateUtente: ",e);
			return false;
		}
		return true;
	}

	/**
	 * metodo per il salvataggio di una nuova utente
	 *
	 * @param utente
	 * @return
	 * @throws Exception
	 */
	@Override
	@Transactional(propagation=Propagation.REQUIRED, readOnly=false)
	public boolean saveUtente(Utente nuovoUtente) throws Exception {

		try{
			Session session = sessionFactory.getCurrentSession();
			session.clear();
			session.save(nuovoUtente);
			session.flush();
			//session.evict(nuovoUtente);
		}
		catch(Exception e){
			log.error("UtenteDaoImpl - saveUtente: ",e);
			return false;
		}
		return true;
	}
}
Spring3_SampleAnnotations
Titolo: Spring3_SampleAnnotations (0 click)
Etichetta:
Filename: spring3_sampleannotation.zip
Dimensione: 8 kB

[Spring] Spring3: dai Controller ai Repository [Parte1]

In questo articolo, riporto la configurazione di Spring per il supporto alle annotations (sia sui model, che sui servizi e DAO) e all’utilizzo di Spring MVC (controller). Ricordiamo che Spring è il framework open source per lo sviluppo di applicazioni su piattaforma Java attualmente più utilizzato e riconosciuto come valida alternativa agli Enterprise JavaBeans (EJB).

In un secondo tutorial (Parte 2), verranno riportati degli esempi di controller (interfaccia che riceve una richiesta HTTP – HttpServletRequest e HttpServletResponse – simile alla Action di Struts), di model o entity (modello dei dati o entità, ossia bean che rappresenta la tabella sulla basedati, con relative associazioni ad altre entità), di service (interfaccia con la logica di business della web application) e di repository (interfaccia DAO che esegue le operazioni CRUD sulla basedati), con relative annotations (@Controller, @Entity, @Service, @Repository).

Grazie all’utilizzo delle annotations, si riduce l’overhead per la scrittura di codice nei file XML, risparmiando tempo anche nel debug e manutenzione del codice (clean code). Il mapping dei bean di Spring, infatti, avviene tutto sul sorgente JAVA, mantenendo anche una netta separazione tra la configurazione del framework e le interfacce/classi della nostra applicazione.

Gli step da seguire per la configurazione di Spring+Hibernate nella nostra web application sono i seguenti:

  1. Installazione delle librerie di Spring e Hibernate con l’ausilio di MAVEN
  2. Integrazione di Spring nella web application (dispatcher, filters, listeners, servlets nel file web.xml)
  3. Logging di Spring e Hibernate
  4. Configurazione del context-application di Spring (configurazione core)
  5. Configurazione dell’ORM Hibernate in Spring
  6. Sicurezza in Spring
  7. Configurazione di Spring MVC

I file di configurazione di Spring sono stati divisi a seconda del modulo/componente che si intende utilizzare nella nostra web application (Spring ORM, Spring Security, Spring MVC, Spring Core), per avere una maggiore leggibilità e flessibilità nella scelta delle componenti da installare. I nomi dei file riportati di seguito (spring-application.xml, spring-servlet.xml, spring-orm.xml, spring-security.xml) non sono imposti dalla specifica di Spring, come neanche la suddivisione adottata.

Continua la lettura