Posts tagged: spring

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 !

Crittare l’url di un immagine in jsp con Spring

DataObfuscationIn questo articolo voglio far vedere come nascondere l’href di un immagine in una pagine JSP, pur visualizzando l’immagine come elemento HTML in modo standard.
Supponiamo di dover sviluppare un’applicazione web in cui ciascun utente mantiene una libreria multimediale, costituita per semplicità da soli elementi immagine. Ogni immagine viene memorizzata all’interno di un database assieme alla relative informazioni dell’utente a cui appartiene.
Affinché l’immagine non sia accessibile semplicemente attraverso l’indirizzo indicato nell’HREF, è necessario offuscare il suo contenuto attraverso una codifica il cui funzionamento è noto al sistema. Lo stesso sistema sarà in grado così di decodificare l’HREF in modo da poter restituire la risorsa e quindi visulalizzarla all’interno della pagina.
Vediamo il seguente esempio:

1
2
3
4
5
   <!-- Immagine in chiaro -->
   <img src="http://host/images/img.jpg" title="Immgine in chiaro" alt="" />
 
   <!-- Immagine offuscata -->
   <img src="http://host/image.htm?a=QVMyJCU0c2Rm%3D&b=QVMyJCU0c2RmMzI0M3NkdzI%3D" title="Immgine in chiaro" alt="" />

Nell’esempio di sopra viene mostrata un’immagine in cui l’href è crittata secondo un algoritmo che ora vedremo. E’ chiaro che nel secondo caso, affinché l’immagine possa essere visualizzata è necessaria la presenza di un elemento capace di interpretarne il contenuto, ottenere e restituire la risorsa.
Nell’ambito di Spring tutto questo può essere tradotto nel modo seguente: è necessario un Controller che venga richiamato ogni volta in cui nella pagina è presente un’immagine.
Il Controller dovrà prelevare il contenuto dell’HREF decodificarlo e restituire l’immagine, se corretto. Procediamo quindi per step.
Primo Step configuriamo il Controller (Servlet) all’interno di Spring…quindi editiamo il file di configurazione di spring ed aggiungiamo le seguenti righe di codice:

1
2
3
4
5
6
7
8
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping" id="urlMapping"> 
        <property name="mappings">
	<value>/image.htm=mediaResolver</value>
        <property value="true" name="alwaysUseFullPath"></property>
    </bean> 
    <bean class="it.devme.obfuscator.media.MediaResolver" id="mediaResolver">
        <property name="dbManager"><ref bean="dbManager"></ref></property>     
    </bean>

L’invocazione della risorsa virtuale index.htm provoca l’invocazione del Controller MediaResolver. Vediamo la sua struttura di seguito:

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
public class MediaResolver extends BaseCommandController {
 
    private DBManager dbManager;
    public void setDbManager (DBManager dbManager) {
	this.dbManager = dbManager;
    }
 
    @Override
    protected ModelAndView handleRequestInternal(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
	Object mediaid = req.getParameter("a");
	Object uid = req.getParameter("b");
 
	if (mediaid==null || uid==null) 
	    return new ModelAndView(new MediaView("image/jpeg", "no-image.jpg", rs.getString("basePath"), MediaView.USE_DEFAULT_IMAGE));
 
	Encrypter dec = Encrypter.getInstance();
	int idaccount = Integer.parseInt(URLDecoder.decode(dec.decrypt(uid.toString()), "UTF-8"));
	int idelement = Integer.parseInt(URLDecoder.decode(dec.decrypt(mediaid.toString()), "UTF-8"));
 
	MediaBean media = null;
	File f = null;
	String mediaPath = null;
 
	/* Check if media item exists */
	media = dbManager.getImage(idelement, idaccount);
	if (media==null) return new ModelAndView();
	mediaPath = media.getPath();
	f = new File(UPLOAD_PATH + File.separator + mediaPath);
 
	MimetypesFileTypeMap mime = new MimetypesFileTypeMap();
 
	resp.setHeader("Cache-Control", "private,no-cache,no-store");
	resp.setContentType(mime.getContentType(f));
	return new ModelAndView(new MediaView(mime.getContentType(f), mediaPath, rs.getString("basePath"), !MediaView.USE_DEFAULT_IMAGE));
    }
 
}

La classe di sopra estende BaseCommandController e quando invocata viene richiamato l’unico metodo di cui è stato fatto l’override che si occupa di decodificare e restituire la risorsa. Vediamo come.
Sostanzialmente recupera i parametri dalla GET (nel nostro caso sono a e b). Li decodifica utilizzando un algoritmo di crittografia a noi noto (si può usare qualunque algoritmo a nostro piacimento basato su una chiave provata) e quindi ottiene per il parametro a, l’id dell’utente a cui appartiene la risorsa e per il parametro b, l’id della risorsa a cui si vuole accedere. Questi dati vengono passati in input ad un metodo che effettua una query su db (getImage(idelement, idaccount)) e verifica se effettivamente la risorsa richiesta appartiene all’utente corrente.
In caso contrario viene restituita una View vuota, altrimenti si determina il mime-type della risorsa e si restituisce una view creata a partire dalla classe MediaView.

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
public class MediaView extends AbstractView {
 
    private String media;
    private String basePath;
    private boolean noImage;
 
    public MediaView(String mime, String media, String basePath, boolean noImage) {
	this.media = media; 
	setContentType(mime);
	this.basePath = basePath;
    }
 
    @Override
    protected void renderMergedOutputModel(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
	String rpath = 	null;
 
	rpath = this.basePath + request.getContextPath().substring(1) +
		MediaResolver.UPLOAD_PATH + File.separator + this.media;
 
 
	File f = new File(rpath);
	byte[] bytes = FileUtils.readFileToByteArray(f);
 
 
        // 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 classe MediaView non fa altro che leggere fisicamente i byte dell’immagine e restituirla nella response utilizzando un OutputStream. Il risultato è che l’immagine scritta nella response verrà visualizzata in pagina come un elemento HTML standard, mantenendo perà l’HREF dell’immagine crittato.
Alla prossima.

 

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