Live Search con Spring e JQuery

live_searchTempo fa in questo articolo abbiamo visto come fare ad integrare il supporto Ajax all’interno del contesto di Spring utilizzando il framework DWR. In questi giorni invece ho deciso di non utilizzare DWR nell’applicazione a cui sto lavorando perché non volevo aggiungere dell’overhead. Per cui ho deciso di risolvere il problema con i soli strumenti messi a disposizione sia da Spring che da javascript.

Per prima cosa individuiamo la libreria javascript che fa per noi, provate ad indovinare cosa useremo ? JQuery esatto, il framework Javascript ormai famoso….rimando al sito per i dettagli di implementazione. Ciò detto passiamo a Spring, decidiamo subito che sarà un controller ad hoc che si occuperà della live search, eseguendo la query sulla nostra tabella e restituendo i risultati in una collection di oggetti noti.
Nel file devme-servlet.xml, file di definizione dei componenti spring della nostra applicazione, andiamo a definire il mapping con il nostro controller: 

<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="mappings">
	<value>
	    /live.me=liveResolver
	</value>
    <property name="alwaysUseFullPath" value="true"/>
</bean>
 
<bean id="liveResolver" class="it.devme.live.LiveSearch">
	<property name="liveManager"><ref bean="liveManager"/></property>
</bean>

In questo modo ogni richiesta all’indirizzo live.me verrà gestita dal nostro controller liveResolver. Passiamo al controller:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class LiveSearch extends BaseCommandController {
 
    private LiveSearchManagerImpl liveManager;
    public void setLiveManager(LiveSearchManagerImpl liveManager) {
	this.liveManager = liveManager;
    }
 
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
	Object lsid = request.getParameter("lsid");
	Object q = request.getParameter("q");
	if (lsid==null || q==null) return new ModelAndView();
 
	int live = Integer.parseInt(lsid.toString());
	String key = q.toString();
	List result = null; 
	switch (live) {
	    // searchOnCity
	    case 1:
    	        result = liveManager.searchOnSchoolsData(key);
		if (result==null) return new ModelAndView();
		    break;
	   default:
		return new ModelAndView();
	}
 
	String content = "";
	for (String item : result) {
	     content += item;
	}
	return new ModelAndView(new LiveSearchView(content));
    }
}

Come si può vedere la classe in se è molto semplice.
Estende BaseCommandController per il quale è stato fatto override del metodo handleRequestInternal(HttpServletRequest request, HttpServletResponse response) così da gestire in modo personalizzato la richiesta. Osserviamo che all’interno della classe c’è l’inject della classe manager impl, ovvero il gestore del database che si preoccupa di eseguire il metodo di interrogazione e quindi di restituire i risultati impacchettando gli oggetti all’interno di una Collection.

/**
 * Effettua una ricerca sulla tabella target delle scuole.
 * 
 * @param key, chiave da ricercare
 * @return result, stringa dei risultati formattati secondo 
 */
@SuppressWarnings("unchecked")
public List<string> searchOnSchoolsData(String key) {
    try {
	String sql = 	"SELECT name, address, city " +
			"FROM \"Schools\" " +
			"WHERE name ILIKE ? OR address ILIKE ? OR city ILIKE ?" +
			"ORDER BY name, city";
 
	List<string> items = (List<string>) jdbcTemplate.query(sql,
			new Object[]{ Utility.wrapWithWildcards(key.trim()), 
                                      Utility.wrapWithWildcards(key.trim()),
                                      Utility.wrapWithWildcards(key.trim()) },
		        new RowMapper() {
			    public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
		                String result = "";
				result += rs.getString("name") + "|" + 
					  rs.getString("address") + "|" +
					  rs.getString("city") + "\n";
				return result;
			    }
	});
	log.info("Search on schools with key "+key);
	return items;
    } catch (EmptyResultDataAccessException e) {
	return null;
    }  catch (DataAccessException dae) {
	dae.printStackTrace();
	log.info("Error while searching on schools.");
	return null;
    }
}

Per completezza riportiamo il metodo del managerImpl che estrae i dati dal DB. Si osservi che la collection restituita altro non è che una lista di stringhe in cui ciascun elemento è l’insieme dei dati delle scuole separati dal carattere separatore ‘|’ e terminati da uno ‘\n’.
Il metodo handleRequestInternal estrae dalla request 2 parametri che sono: lsid e q. Il primo serve ad indicare quale metodo di recupero dei dati eseguire; il secondo indica la chiave della ricerca. Nel nostro esempio c’è solo un metodo per il recupero dei dati, ma nulla vieta di aggiungerne quanti più se ne desidera.

I dati estratti vengono concatenati all’interno di una stringa passata al costruttore della view personalizzata (si veda questo post), come segue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
 * @author mulp @ devme
 *
 */
public class LiveSearchView extends AbstractView {
 
    private String content;
 
    public LiveSearchView(String content) {
	this.content = content;
    }
 
    @Override
    protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
	byte [] bytes = content.getBytes();
        // Write content type and also length (determined via byte array).
        response.setContentType(getContentType());
        response.setContentLength(bytes.length);
 
        // Flush byte array to servlet output stream.
        ServletOutputStream out = response.getOutputStream();
        out.write(bytes);
        out.flush();		
    }
 
}

La view nel suo metodo renderMergedOutputModel realizza la scrittura di quanto estratto dal db all’interno della response, nella quale è stato precedentemente fissato il tipo e la lunghezza del contenuto. Questa azione comporta materialmente la comparsa nella pagina web del contenuto estratto, più precisamente viene intercetatto dal metodo javascript che formatta opportunamente il risultato. Di seguito vediamo i javascript che servono per realizzare la live search e lo scorcio di pagina che contiene il campo di testo sul quale avviene la live search.

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
<link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/themes/base/jquery-ui.css" rel="stylesheet" type="text/css"/>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8/jquery-ui.min.js"></script>
<script type="text/javascript" src="/script/jquery/jquery.autocomplete.js"></script>
 
<script type="text/javascript">
$(document).ready(function() {
	function formatItem(row) {
		return row[0] + "," + row[2] + "," + row[1];
	}
	function formatResult(row) {
		return row[0] + "," + row[1] + "," + row[1];
	}
 
	$("#ls_school").autocomplete('/live.me?lsid=1', {
		width: 400,
		matchContains: true,
		formatItem: formatItem,
		formatResult: formatResult,
		max: 100
	});
});
 
.....
.....
<input type="text" name="ls_school" id="ls_school" />
.....
....

Il codice Javascript di sopra è il binding con il componente JQuery dell’autocomplete (compreso il css) il quale trasforma la digitazione dei caratteri all’interno del campo di testo in una chiamata HTTP all’indirizzo http://www.devme.it/live.me?lsid=1&q=devme. Lato back-end risponde il nostro controller il quale effettua la ricerca e restituisce il risultato come descritto sopra. Infine il risultato verrà formattato a modi tendina da JQuery.
Come si vede è molto semplice gestire la live search o, in generali, componenti che richiedono interazione Ajax con questa tecnica. Spero di riuscire a mostrare altri esempi diversi, più avanti in altri articoli.
Stay tuned !

iPhone: SQLite e singleton

iphone-guitarQuesto è il mio articolo sull’iphone e per l’occasione volevo parlare dell’utilizzo di SQLite all’interno della gestione del pattern singleton. SQLite è un insieme di librerie che implementano un motore di database self-contained che per la sua leggerezza e compatibiltià con gli standard SQL risulta essere il database più diffuso. Siccome è molto leggero richiede poche risorse, quindi ha ha senso che venga utilizzato all’inteno di dispositivi embeeded quali ad esempio l’iphone. l’iPhone implemente le librerie di SQLite fornendo così il supporto al database.

Passiamo subito ad un esempio pratico dando per scontato un conoscenza di base del linguaggio objective-c.
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//
//  DBHandle.h
//
//  Created by mulp on 11/05/10.
//
 
#import <Foundation/Foundation.h>
#import <sqlite3.h>
 
/* Define database name */
extern NSString * const databaseName;
 
@interface DBHandle : NSObject {
    /* Database variables */
    NSString *databasePath;
 
    /* Database handle */
    sqlite3 *dbconn;
}
 
@property (nonatomic, retain) NSString *databasePath;
 
/** 
 * Get singleton instance of this class, or rather
 * the only database handle.
 */
+(DBHandle*) getInstance;
 
/**
 * Connect to database.
 */
-(int) connectDB: (NSString*) path;
 
/**
 * Disconnect to database.
 */
-(void) disconnectDB;
 
/**
 * Check if the SQL database has already been saved to the users phone, 
 * if not then copy it over.
 */
-(void) checkAndCreateDatabase;
 
 
-(NSMutableArray*) getList;
@end

Analizziamo il file header della classe che sara DBHandle. La definizione dello header è molto semplice consiste in una costante che indica il nome del database, i classici metodi di connessione e disconnessione dal db. Un metodo (statico) per indenderci quello che ha il segno + davanti, che può essere invocato senza aver bisogno di instanziare la classe, che permette di ottenere l’istanza singleton della classe. Ed infine un metodo che verifica la presenza del database e all’occorrenza lo crea ed un metodo che restituisce la lista di elementi presenti all’interno di una tabella di esempio. Esistono altri metodi che non compaiono all’interno della definizione del file header in quanto privati alla classe DBHandle che andiamo a vedere.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//
//  DBHandle.m
//
//  Created by mulp on 11/05/10.
//
 
#import "DBHandle.h"
 
// Usage: [[DBHandle getInstance] method_name];
 
@implementation DBHandle
 
@synthesize databasePath;
 
static DBHandle *instance = nil;
NSString * const databaseName = @"devme.sql";
 
+(DBHandle*) getInstance {
    @synchronized([DBHandle class]) {
	if (!instance)
	    [[self alloc] init];
	    return instance;
	}
    return nil;
}
 
+(id) alloc {
    @synchronized([DBHandle class]) {
	NSAssert(instance==nil, @"Attempted to allocate a second instance of a singleton");
	instance = [super alloc];
	return instance;
    }
    return nil;
}
 
-(id) init {
    self = [super init];
    if (self != nil) {
	[self checkAndCreateDatabase];
    }
    return self;
}
 
-(id) retain {
    return self;
}
 
-(int) connectDB:(NSString *)path {
    // Open the database.
    int status = sqlite3_open([path UTF8String], &dbconn);
    if (status != SQLITE_OK) {
        // Even though the open failed, call close to properly clean up resources.
        sqlite3_close(dbconn);
        NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(dbconn));
        // Additional error handling, as appropriate...
    }
    return status;
}
 
-(void) disconnectDB {
    sqlite3_close(dbconn);
}
 
-(void) checkAndCreateDatabase{
 
    // Get the path to the documents directory and append the databaseName
    NSArray *homePaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *homeDir = [homePaths objectAtIndex:0];
    self.databasePath = [homeDir stringByAppendingPathComponent:databaseName];
 
    BOOL success;
 
    // Create a FileManager object, we will use this to check the status
    // of the database and to copy it over if required
    NSFileManager *fileManager = [NSFileManager defaultManager];
 
    // Check if the database has already been created in the users filesystem
    success = [fileManager fileExistsAtPath:databasePath];
 
    // If the database already exists then return without doing anything
    if(success) return;
 
    // If not then proceed to copy the database from the application to the users filesystem
 
    // Get the path to the database in the application package
    NSString *databasePathFromApp = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:databaseName];
 
    // Copy the database from the package to the users filesystem
    [fileManager copyItemAtPath:databasePathFromApp toPath:databasePath error:nil];	
    [fileManager release];
}
 
-(NSMutableArray*) getItemsList {
    NSMutableArray * container = [[NSMutableArray alloc] init];
 
    NSLog(@"Path is: %@", databasePath);
    // Open the database from the users filessytem
    if(sqlite3_open([self.databasePath UTF8String], &dbconn) == SQLITE_OK) {
	// Setup the SQL Statement and compile it for faster access
	const char *sqlStatement = "SELECT strftime('%s', publish_days) / 86400, title FROM devme ORDER BY publish_days DESC";
	sqlite3_stmt *compiledStatement;
 
	if(sqlite3_prepare_v2(dbconn, sqlStatement, -1, &compiledStatement, NULL) != SQLITE_OK) {
	    NSLog(@"Error: failed to prepare stmt with message %s", sqlite3_errmsg(dbconn));
	}
 
	DevMe *devme;
	while(sqlite3_step(compiledStatement) == SQLITE_ROW) {
	    devme = [[Devme alloc] init];
	    // Read the data from the result row
	    devme.pdate = [NSNumber numberWithInt:sqlite3_column_int(compiledStatement, 0)];
	    devme.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(compiledStatement, 1)];
	    [container addObject:devme];
	}
 
	// Release the compiled statement from memory
	sqlite3_finalize(compiledStatement);
	[devme release];
    }
    return container;
}
 
@end

Come vedete anche la definizione della classe è molto semplice. Osserviamo subito la definizione di una variabile static che rappresenta l’istanza della classe singleton. Per ottenerla basta richiamare il metodo getInstance il quale alloca materialmente la memoria per l’oggetto, e lo inizializza invocanto il metodo init. All’interno dell’inizializzazione viene invocato il metodo checkAndCreateDatabase. SQLite memorizza i dati all’interno di un file il quale viene creato al primo accesso al database. Il metodo verifica la presenza del database e se non lo trova crea materialmente il file e lo rende disponibile all’uso. La catena di metodi consente quindi di allocare e inizializzare l’istanza della classe DBHandle e restituirla al chiamante. Sull’istanza della classe sarà poi possibile invocare i metodi di connessione e disconnessione al database e quindi l’invocazione del metodo che restituisce la lista di elementi presenti all’interno di una tabella di esempio del database. Una possibile invocazione potrebbe essere la seguente:

dbhandle = [DBHandle getInstance];
[dbhandle connectDB:@"devme.sql"];
NSMutableArray *datasource = [dbhandle getItemsList];
[dbhandle disconnectDB];

Il metodo getItemsList esegue una query sul database e preleva i 2 attributi della tabella nel nostro esempio devme. I 2 attributi estratti vengono assegnati alle proprietà dell’oggetto Devme il quale viene poi aggiunto all’interno dell’array container. Quindi in definitiva il container contiene una lista di oggetti del tipo Devme.

In questo modo utilizzando un singleton si ha la possibilità da qualunque punto della nostra applicazione di accedere all’unica istanza della classe manager del database evitando così eventuali errori di gestione della connessione e ottimizzando l’accesso al database stesso. Nei prossimi articoli vedremo esempi di utilizzo di oggetti grafici di base messi a disposizione dall’SDK dell’iphone.

Stay tuned !

Spring Web Flow tutto d’un fiato ! [Part 2/2]

spring-webflowRieccoci qui per continuare il discorso iniziato nel post precedente su SFW, il framework per la creazione di applicazioni web. Dopo aver definito gli elementi facenti parte della definizione di un flusso (flow) vediamo come è possibile definirlo, attraverso la sua specifica XML. Come detto in precedenza, il flow ha inizio grazie al flow executor un componente interno di SWF. L’executor da inizio al flow a partire dallo stato configurato come attributo nel tag root della definizione del flow:

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
      http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd"
      start-start="loginState">
	<-- Add your definition here -->
</flow>

loginState rappresenta lo stato iniziale da cui avrà inizio l’esecuzione del flow. Come già detto gli stati sono gli elementi di base della definizione di un flow, in cui è possibile transire da uno stato ad un’altro effettuando delle operazioni, dei controlli o visualizzare dell’output. Tra i possibili stati che possono essere utilizzati vi è il View State, il quale viene utilizzato per visualizzare o chiedere informazioni agli utenti. Una view può essere una qualunque pagina JSP, ma può anche essere una qualunque view supportata da Spring MVC. Di seguito viene riassunto l’elenco di possibili elementi figli del view state:

  • <attribute>, dichiara un attributo che descrive lo stato.
  • <binder>, usato per configurare un custom form binding.
  • <exception-handler>, riferisce un bean che implementa un FlowExecutionExceptionHandler che gestisce tutte le eccezioni sollevate nello stato corrente.
  • <on-entry>, indica quali azioni devono essere eseguiti quando si entra in questo stato.
  • <on-exit>,  indica quali azioni devono essere eseguiti quando si esce da questo stato.
  • <on-render>. indica quali azioni devono essere eseguiti quando si visualizza questo stato.
  • <secured>, viene utilizzato assieme a Spring Security per restringere l’accesso a questo stato.
  • <transition>, definisce un percorso da questo stato ad un’altro basato sul verificarsi di un evento o un’eccezione.
  • <var>, dichiara una variabile.

Ciò detto, il modo più semplice per dichiarare un View State è il seguente:

<view-state id="login"/>

Il tag di sopra definisce una view logica, ovvero non associata a nessuna pagina fisica, da cui non sarà possibile transire in nessun’altro stato. Per associare la view, il nome logico definito dall’attributo id ad una pagina jsp è necessario aggiungere l’attributo view,

<view-state id="login" view="loginForm"/>

in questo modo la view è associata alla pagina loginForm.jsp, la quale verrà visualizzata al caricamento della view. Nota: sarà possibile evitare di dover specificare l’estensione dei file associati alle view, configurando opportunamente Spring MVC View Resolver.

Ma vediamo un’esempio un pò più completo:

<view-state id="login" view="loginForm">
  <transition on="accountEntered" to="homeUser" />
  <transition to="endState" on="cancel" />
</view-state>

l’esempio di sopra definisce due elementi figli del tag view, elementi transition. Il primo dei due indica una transizione dallo stato corrente allo stato homeUser, al verificarsi incondizionato dell’evento accountEntered. Il secondo definisce una transizione dallo stato corrente allo stato endState al verificarsi dell’evento cancel. Come definiamo questo tipo di eventi ? Molto semplicemente….lo scatenarsi degli eventi avviene direttamente dalle view fisiche associate. Consideriamo la view loginForm.jsp associata alla view logica login come segue:

<form:form>
   <!-- HTML form definition here -->
   <input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}"/>
   <input type="submit" name="_eventId_accountEntered" value="Entra" />
   <input type="submit" name="_eventId_cancel" value="Annulla" />
</form:form>

la pagina web contiene tra gli altri elementi che ospiteranno i dati di accesso dell’utente, il pulsante di submit della pagina e un campo hidden. Quest’ultimo è richiesto per riconoscere l’id del flow in cui ci si trova ed è sempre necessario. I pulsanti di submit, oltre al loro classico funzionamento servono anche a scatenare gli eventi che abbiamo definito nel file di flow. Si noti il nome del pulsante submit Entra che invia i dati del form al server e serve per effettuare l’autenticazione dell’utente. In particolare il nome del pulsante ha un prefisso _eventId_ il quale indica a SWF che alla pressione del bottone dovrà scatenare un evento il cui nome è ciò che segue il prefisso, nel nostro caso accountEntered. In questo modo è possibile catturare l’evento nel file di definizione del flow. Stesso discorso vale per il bottone cancel.
E’ possibile anche scatenare un’evento attraverso un link, ad esempio:

<a href="${flowExecutionUrl}&_eventId=accountEntered">Entra</a>

Altro modo consiste nel creare un acampo hidden all’interno del form con name _eventId.

Dopo aver analizzato il ViewState che permette all’utente di essere coinvolto all’interno del flow, passiamo a vedere l’ActionState il quale definisce alcuni elementi che permettono di effettuare delle elaborazioni all’interno del flow. L’elenco dei nodi figli dell’action state è il seguente:

  • <attribute>, dichiara un attributo che descrive lo stato
  • <evaluate>, valuta un’espressione assegnando opzionalmente il risultato.
  • <exception-handler>, referenzia un bean che implementa FlowExecutionExceptionHandler il quale gestisce le eventuali eccezioni per lo stato corrente.
  • <on-entry>, definisce un’azione la quale viene eseguita all’ingresso dello stato.
  • <on-exit>, definisce un’azione la quale viene eseguita all’uscita dello stato.
  • <render>, richiede che la prossima view renderizzi un frammento di contenuto.
  • <secured>, restringe l’accesso allo stato corrente richiedendo l’inserimento degli attributi utente.
  • <set>, imposta una variabile all’interno dello scope del flow.
  • <transition>, definisce una transizione verso uno stato al verificarsi di un evento. Lo stato di destinazione può coincidere con lo stato corrente.

Quindi vediamo un’esempio d’uso dell’ActionState:

<action-state id="homePage">
	<evaluate expression="devME.authenticateUser(username, password)" />
	<transition to="home" on="userOK" />
	<transition to="loginForm" on="userKO" />
</action-state>

Il codice di sopra definisce il seguente comportamento. Dopo che l’utente ha inserito i propri dati di accesso all’interno del form e clicca sul pulsante Entra viene scatenato l’evento accountEntered, il quale prevede una transizione nello stato homeUser, un’ActionState. All’interno viene valutata un’espressione la quale esegue un metodo di autenticazione dell’utente sulla base dei dati forniti. Lo stesso metodo scatena 2 eventi diversi: userOK se l’utente è stato autenticato, dal quale si transisce verso la home dell’utente. L’evento userKO se l’utente non è stato autenticato e quindi si ritorna alla view di login. Come vedete è molto semplice e intuitivo.
Stay tuned.

Spring Web Flow tutto d’un fiato ! [Part 1/2]

spring-webflowSpring Web Flow è un framework basato su Spring MVC che permette lo sviluppo di applicazioni flow-based. In questo articolo vedremo come aggiungere Spring Web Flow ad applicazioni Spring-based e definire alcuni flussi tra le applicazioni e i loro utenti.
Iniziamo col dire che tutti i flussi (da ora in avanti li chiameremo con loro vero nome, flow) sono costituiti da 3 elementi base, ovvero:

  • States
  • Transitions
  • Flow data

States: indica ciò che accade all’interno di un flow. Spring Web Flow definisce 5 tipi di States differenti: View, Active, End, Subflow, Decision. Vedremo più avanti come questi tipi di stato partecipano alla creazione di un flow.
Transitions: sono di fatto il mezzo di collegamento tra i vari States. Una View state, può contenere al suo interno un certo numero di transizioni ciascuna delle quali collega altri stati.
Flow data: all’interno di un flow vengono collezionati alcuni dati che dipendono dagli stati in cui il flow si trova o si trovava. La visibilità dei dati di ciascun flow dipende dallo scope con cui questo viene definito. Per la precisione esistono 5 tipi di scope differenti in SWF che sono:
 

  • Flow scope : viene creato all’inizio del flow e distrutto quando il flow termina. I dati all’interno del Flow scope sono accessibili e disponibili per tutta la durata del flow
  • Conversation scope : viene creato all’inizio di un top-level flow e distrutto quanto il flow termina. Il conversation scope è simile al flow scope, eccetto per l’accesso ai dati. Infatti mentre il flow scope è accessibile solo dal flow che l’ha creato, il conversation flow può essere acceduto sia dal top-level flow che da tutti i suoi subflow.
  • Request scope : viene creato all’inizio di una richiesta HTTP e distrutto al termine di tale richiesta. I dati all’interno del request scope sono disponibili a tutti gli stati del flow per l’intera durata della richiesta.
  • Flash scope : viene creato quando il flow ha inizio, ripulito quando si entra all’interno di una view e distrutto alla fine del flow. I dati all’interno del flash scope sono disponibili a tutti gli states per l’intera durata del flow.
  • View scope : viene creato quando il flow entra all’interno di una view e distrutto quando la view viene renderizzata. E’ il flow che ha più breve durata, per questo motivo i dati all’interno di questo scope sono disponibili solo alla view che li ha creati.

Ciò detto vediamo come fare ad installare ed utilizzare SFW in pochi semplice passi. Naturalmente per poter iniziare a lavorare con SWF è necessario scaricare i jar che lo contengono, questa pagina costituisce il punto di partenza da cui potete trovare info dettagliate sul framework e dal link download potete scaricare le librerie. Le librerie necessario da aggiungere al classpath della propria applicazione sono :

  • org.springframework.WebFlow-2.0.8.RELEASE.jar
  • org.springframework.binding-2.0.8.RELEASE.jar
  • org.springframework.faces-2.0.8.RELEASE.jar
  • org.springframework.js-2.0.8.RELEASE.jar

in realtà WebFlow e Binding sono le librerie principali, mentre faces e js mettono a disposizione il supporto a JSF e Javascript/Ajax. In aggiunta a queste librerie è necessario aggiungere il supporto al linguaggio che aiuta a definire i Flow.
SWF usa OGNL.

SWF come detto si basa su Spring MVC, quindi così come per tutte le applicazioni spring-based, le richieste passano attraverso il DispatcherServlet. Per configurare SWF basta aggiungere le seguenti righe di codice al file servlet.xml :

<servlet>
    <servlet-name>DevMeApp</servlet-name>   
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>

Bene, a questo punto non rimane che indicare quale richiesta debba essere "catturata" da spring e quindi girata a SWF. Per fare questo, ci basterà configurare il nostro DispatcherServlet con un handle per tutte le richieste il cui URL fa match con la stringa "devme"…tradotto:

<servlet-mapping>
  <servlet-name>DevMeApp</servlet-name>
  <url-pattern>/devme/*</url-pattern>
</servlet-mapping>

Ora che abbiamo configurato Spring passiamo alla configurazione di SWF. La configurazione avviene tramite compilazione di file XML di Spring aggiungendo a tali file il namespace di SWF, in modo tale da poter utilizzare i tag messi a disposizione dal framework. Quindi partendo da un file di spring aggiungiamo i seguenti namespace:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:webflow="http://www.springframework.org/schema/webflow-config"
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/webflow-config
           http://www.springframework.org/schema/webflow-config/spring-webflow-config-2.0.xsd">
</beans>

quindi aggiungiamo la nostra configurazione all’interno del tag beans.
La prima cosa da fare è aggiungere un riferimento al flow executor, ovvero all’elemento che gestisce l’esecuzione del flow. Aggiungiamo quindi il tag:

<webflow:flow-executor id="flowExecutor"/>

Il secondo step è quello di indicare a SWF dove trovare il file xml della definizione del flow. Questo viene fatto attraverso il tag flow registry che permette di indicare la posizione su file system in cui risiedono i file XML dei vari flow. Per esperienza, è molto comodo creare i file di flusso separati all’interno di ogni singolo file.
Aggiungiamo quindi il flow registry:

<webflow:flow-registry id="flowRegistry" base-path="/WEB-INF/flows-defs">
	<webflow:flow-location id="devme" path="/WEB-INF/flows/devme-flow.xml" />
</webflow:flow-registry>

l’attributo id del tag flow location permette di assegnare un id al flow di modo che possa essere riferito all’occorrenza. In assenza dell’attributi id, l’id del flow corrisponderà al nome del file del flow.

SWF fornisce un handler adapter (spring based) che permette che fa da bridge tra il DispatcherServlet e il flow executor, gestendo la richiesta e manipolando il flow. E’ necessario configurare l’handler all’interno del file di configurazione affinché possa essere invocato dal framework. Aggiungiamo quindi il seguente tag bean:

<bean class="org.springframework.WebFlow.mvc.servlet.FlowHandlerAdapter">
  <property name="flowExecutor" ref="flowExecutor" />
</bean>
<bean class="org.springframework.WebFlow.mvc.servlet.FlowHandlerMapping">
  <property name="flowRegistry" ref="flowRegistry" />
</bean>

in questo modo il DispatcherServlet è in grado di stabilire a chi girare la richesta consultando uno o più handler. Nel codice di sopra abbiamo aggiunto un riferimento al FlowHandlerMapping il quale mantiene un riferimento al FlowRegistry in modo tale da girare la richiesta al flow corretto. Un’applicazione basata su SWF tipicamente contiene più di un flow, per questo il collegamento tra FlowHandlerMapping e flow registry consente di selezionare il flow corretto sulla base della richiesta ricevuta.

Dopo aver configurato la nostra applicazione Spring fornendo il supporto a SWF vediamo come definire un file di flow. Un file di flow è un file XML diverso da quello visti finora, nel senso che ha un nodo radice diverso il quale appartiene ad un namespace differente.
 

<flow xmlns="http://www.springframework.org/schema/webflow"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.springframework.org/schema/webflow
      http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
	<-- Add your definition here -->
</flow>

Vediamo quali sono gli elementi che contribuiscono alla definizione di un flow. Di seguito viene mostrato un elenco di tali elementi con per ciascuno una breve descrizione.

  • <action-state>, definisce una o più azioni, al termine delle quali si può transire verso uno stato successivo all’interno del flow.
  • <attribute>, consente di definire un attributo da memorizzare all’interno del flow. Viene utilizzato assieme al tag <value>.
  • <bean-import>, importa un elemento bean definito dall’utente.
  • <decision-state>, permette di valutare un’espressione sulla base della quale decidere verso quale stato transire.
  • <end-state>, indica lo stato finale del flow. La transizione su questo stato comporta il termine del flow.
  • <exception-handler>, indica un bean che può gestire eccezioni per questo flow.
  • <global-transition>, definisce una o più transizioni che sono disponibili da tutti gli stati.
  • <input>, definisce un input per questo flow.
  • <on-end>, evento che definisce l’azione che viene richiamata quando il flow termina.
  • <on-start>, evento che definisce l’azione che viene richiamata quando il flow inizia.
  • <output>, definisce un output per questo flow.
  • <persistence-context>, crea e alloca un contesto quando il flow ha inizio. Viene usato assieme al transaction manager.
  • <secured>, viene usato per restringere l’accesso ad un determinato stato.
  • <subflow-state>, invoca un nuovo flow come sotto-flow di quello corrente.
  • <var>, definisce una variabile con flow scope.
  • <view-state>, indica uno stato che si presenta all’utente tipicamente caricando un certo output e richiedendo l’interazione da parte dell’utente.

Per evitare di dlungarmi più di quanto abbia già fatto rimando la continuazione di questo articolo al post successivo. Continuo a scrivere quandi non passerà molto dalla pubblicazione seguente…prometto :P  !
Stay tuned.

Il backup di linux

backupIn questo ultimo periodo mi sto occupando di trovare un metodo di backup da implementare per il server che amministro. Ho dato un occhiata a diversi sistemi che consentono di effettuare l’operazione di backup dei dati, ma al momento non ho ancora scelto quello che fa per me. In più ho postato anche sul forum del sito debianizzati.org un’ottima risorsa per la comunità Debian con gente in gamba disponibile ad aiutare altri in difficoltà. Quindi essendo un’altro in difficoltà, ho deciso di scrivere sul forum chiedendo loro un parere, al fine di risolvere il problema e di creare un guida più o meno completa del problema del backup su linux.
Vi siete mai chiesti quale sia lo strumento di backup utilizzato da wikipedia ? Sinceramente sì, ma non so come possa essere implementato…mi vengono in mente cluster di server che fanno solo quello con tera e tera disponibili utilizzati per salvare i dati.
E riferiamoci proprio a Wikipedia per la definizione di backup: "Il backup, copia di sicurezza o copia di riserva in informatica indicano un’operazione atta a prevenire la perdita totale dei dati archiviati nella memoria di massa dei computer siano essi stazione di lavoro o server. L’attività di backup è un aspetto fondamentale della gestione di un computer: in caso di guasti, manomissioni, furti, ecc., ci si assicura che esista una copia dei dati, pertanto l’esecuzione del backup è quasi sempre automatica e svolta normalmente con una periodicità stabilita (per esempio una volta al giorno o alla settimana)."
Su Linux esistono diversi strumenti per effettuare il backup dei dati…uno di questi è dd, un tool che si usa da riga di comando mooolto potente. Nei prossimi articoli riguardanti questo argomento vedremo altri tool.

Il comando dd

Il comando dd permette di copiare i dati in blocchi, effettuando delle conversioni all’occorrenza. E’ molto facile da utilizzare, in sostanza è necessario indicare il valore di 2 parametri.

devme:~$ dd if=/dev/sda1 of=/media/backup.img

Nell’esempio di sopra è richiesto a dd di effettuare la copia della prima partizione dell’unità sda all’interno dell’immagine backup.img. Al termine, il file di immagine corrisponderà alla partizione sorgente e sarà possibile montarlo grazie al comando mount. Vedremo più avanti come.

devme:~$ dd if=/dev/sda of=/media/sdb

In questo caso invece viene effettuata il clone del disco sda all’interno del disco sdb. Si noti che la copia in questo caso prevede la copia eventuale anche del MBR , master boot record e dell’eventuale partizione di swap.
La sintassi base del comando dd è la seguente:

devme:~$ dd if=<source></source> of=<target> bs=<byte size=""> skip= seek= conv=<conversion> </conversion></byte></target>

Per effettuare l’operazione simmetrica, ovvero il ripristino dei dati da un backup, è sufficiente invertire il valore dei parametri if e of. Vediamo ora una serie di esempi di utilizzo del comando dd.

Effettua la duplicazione di una partizione in un’altra partizione, copiando i dati in blocchi da 4kb, effettuando le conversioni: notrunc=non troncare il file di output,noerror=non fermarti in caso di errore nella lettura di un blocco di dato.

devme:^$ dd if=/dev/sda2 of=/dev/sdb2 bs=4096 conv=notrunc,noerror

Crea un’immagine ISO di un cd, leggendo in blocchi da 2kb, effettuando le conversioni: sync fa padding dei blocchi letti con bye nulli, noerror non fermarti in caso di errore nella lettura di un blocco di dato.

devme:^$ dd if=/dev/hdb of=/home/devme/cdrom.iso bs=2048 conv=sync,notrunc

Crea un’immagine ISO di un cd, leggendo in blocchi da 2kb, effettuando le conversioni: sync fa padding dei blocchi letti con bye nulli, noerror non fermarti in caso di errore nella lettura di un blocco di dato. Successivamente sarà possibile montare l’immagine creata usando il comando mount.

devme:^$ dd if=/dev/hdb of=/home/devme/cdrom.iso bs=2048 conv=sync,notrunc
devme:^$ mount -o loop /home/devme/cdrom.iso /mnt/iso_images

Effettuare una cancellazione sicura a prova di bomba, ovvero senza la possibilità di poter risalire ai dati scritti (secure delete).

1
2
#!/bin/bash 
for n in `seq 7`; do dd if=/dev/urandom of=/dev/sda bs=8b conv=notrunc; done

Modo semplice e veloce per effettuare un backup del proprio portatile. Segue comando per effettuare il ripristino.

devme:^$ dd if=/dev/hda of=/dev/sda bs=64k conv=notrunc,noerror
devme:^$ dd if=/dev/sda of=/dev/hda bs=64k conv=notrunc,noerror

Crea il backup di un hard disk e memorizzalo all’interno di una serie di DVD. Seguono comandi per il ripristino.

devme:^$ if=/dev/sda3 of=/home/devme/dvd_set_1.img bs=1M count=4430
devme:^$ dd if=/dev/sda3 skip=4430 of=/home/devme/dvd_set_2.img bs=1M count=4430
devme:^$ dd if=/dev/sda3 skip=4430 of=/home/devme/dvd_set_3.img bs=1M count=4430
devme:^$
devme:^$ dd if=/media/devme/devme_set_1.img of=/dev/sda3 bs=1M conv=sync,noerror
devme:^$ dd if=/media/devme/devme_set_2.img of=/dev/sda3 seek=4430 bs=1M conv=sync,noerror
devme:^$ dd if=/media/devme/devme_set_3.img of=/dev/sda3 seek=4430 bs=1M conv=sync,noerror

Copia il master boot record, ma non la tabella delle partizioni.

devme:^$ dd if=/dev/sda of=/home/devme/devme_mbr.img bs=446 count=1

Vediamo ora un caso particolare, ovvero fare il backup completo di un disco, quindi con MBR, tabella delle partizioni e tutte le partizioni del disco, e vediamo come fare a selezionare solo la partizione da montare con il comando mount. Quindi supponiamo di avere:

devme:^$ dd if=/dev/sda of=my_disk_image.img

con il quale effettuiamo il backup creando il nostro file immagine my_disk_image.img che contiene al suo interno l’intero disco. Per montare solo una certa partizione dovremmo sapere l’offset in cui inizia la partizione e quindi montarla con il comando mount sul dispositivo di loop. Per determinare l’offset dei vari elementi costituenti il disco utilizziamo un’altro comando di linux che consente di far questo, parted. Lanciando parted sul file appena creato otteniamo:

devme:^$ parted my_disk_image.img
GNU Parted 1.7.1
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) unit
Unit?  [compact]? B
(parted) print
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Number  Start        End           Size         Type     File system  Flags
1      32256B       106928639B    106896384B   primary  ext3         boot
2      106928640B   1184440319B   1077511680B  primary  linux-swap
3      1184440320B  10256924159B  9072483840B  primary  ext3
(parted) quit

a questo punto possiamo montare il file system che ci interessa indicando l’offset specifico, ovvero:

devme:^$ mount -o loop,ro,offset=32256 my_disk_image.img /mnt/devme

E qui mi fermo, anche perché potrei continuare per delle ore con degli esempi su dd, alcuni dei quali dimostrano quanto veramente potente è questo comando. Nei prossimi articoli vedremo altri strumenti per realizzare il backup.
Stay tuned !

 

You need to log in to vote

The blog owner requires users to be logged in to be able to vote for this post.

Alternatively, if you do not have an account yet you can create one here.

Powered by Vote It Up

WordPress Themes