Nel corso degli anni, lo sviluppo di applicazioni web, e in particolare delle interfacce utente, è cambiato radicalmente come anche la rappresentazione e il raggruppamento delle informazioni. Il classico modello a directory, tipico delle prime applicazioni web è stato, negli ultimi anni, rimpiazzato da un modello meno gerarchico, cosiddetto a tag, dove ad un elemento informativo può essere associata più di una categoria (come avviene nel modello ad albero di directory). Le cosiddette applicazioni web 2.0 hanno portato diversi modelli di comunicazione visuale, che guidano in maniera più diretta l'utente permettendogli di fruire del servizio in maniera immediata.
Le tante applicazioni di social network, i nuovi portali pensati per una perfetta interazione con l'utente, hanno adottato una serie di tecniche e convenzioni che rendono la navigazione dei siti web molto familiare, pur passando da un sito ad un altro. Dal punto di vista dell'usabilità tutto questo si traduce in un beneficio per l'utente che non si trova costretto ad ogni nuovo sito di ricostruire una mappa mentale, ma può ricorrere all'uso di elementi con cui ha ormai familiarità.
L'articolo ci mostrerà come sviluppare uno di questi tipici elementi, la tag cloud, introdotta per mostrare un insieme di categorie dando rilievo a quelle più importanti. Si tratta di un insieme di etichette (tag) che non hanno un ordinamento particolare, racchiuse all'interno di un box, ognuna di esse con una formattazione tale che le fa risaltare (se importante) agli occhi degli utenti del sito web.
Come dicevamo, oltre che di un semplice modo di rappresentare le informazioni, utilizzare un sistema di etichette vuole dire organizzare le informazioni in maniera differente. Noi ci occuperemo quindi di mostrare come sia possibile organizzare tali informazioni in un contesto web applicato alla piattaforma Java, mostrando un esempio concreto.
Analisi dei requisiti e progetto
La nostra intenzione è mostrare lo sviluppo di un gestore di tag che, nel nostro caso, sarà sviluppato partendo da zero. Da quanto abbiamo avuto modo di capire, un tag è un'etichetta di una risorsa (una pagina web, una foto, un video, ecc). Molto spesso, quindi, l'utente vuole visualizzare tutte le risorse che sono etichettate da un tag. Altra possibilità che generalmente si dà all'utente è quella di assegnare un'etichetta ad una risorsa (utente che sempre più diventa produttore di contenuti).
Fondamentalmente, quindi i requisiti sono tre:
- visualizza lista risorse associate al tag;
- visualizza dettaglio risorsa;
- aggiungi tag a risorsa.
La nostra analisi dei requisiti è completata dicendo che nel nostro semplice esempio una risorsa è una fotografia (in modo da rendere concreto il progetto), ma l'idea è adattabile a qualsiasi contesto.
Da una rilettura dei requisiti possiamo già estrarre il semplice modello di dominio in uno sviluppo orientato agli oggetti, identificando la classe Risorsa (o Resource, all'inglese) e la classe Tag.
Le nostre immagini (classe Resource) avranno un identificativo, un titolo ed una descrizione (più altri parametri che riterrete opportuni). Il tag, per sua stessa natura, sarà sempre lo stesso, un identificativo, un nome e un peso (weight). Come si vede dal diagramma ogni Resource ha 0 o n tag associati.
Per completezza, andando avanti nella progettazione, mostriamo la classe che si occuperà di gestire la persistenza ed il controller che, di fatto, ricalca i nostri requisiti utente esplicitati (in figura solo i due riferiti direttamente ai tag). Come si vede, il disegno è molto semplice, come anche l'architettura informativa. Partendo da zero risulta estremamente facile, ma modificare un progetto già esistente non dovrebbe essere particolarmente complesso in quanto significa associare le diverse risorse presenti alla classe tag. In più andrà modificato il layer di persistenza (questa operazione è potenzialmente difficoltosa) ed aggiunto il flusso di controllo.
Implementazione persistenza
Utilizzeremo una base di dati relazionale, rifacendoci al design discusso sopra. Quindi, la nostra base di dati avrà la relazione tra la tabella Resource e la tabella Tag, rappresentata dalla tabella ResourceTag (cioè le associazioni tra Resource e Tag). Qui vediamo lo script SQL:
Listato 1. Creazione e inserimento nel db
CREATE TABLE 'tagcloud'.'Resource' (
'id' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
'title' VARCHAR(45) NOT NULL,
'description' VARCHAR(45) NOT NULL,
PRIMARY KEY ('id')
)
ENGINE = InnoDB;
CREATE TABLE 'tagcloud'.'tag' (
'id' INTEGER UNSIGNED NOT NULL AUTO_INCREMENT,
'name' VARCHAR(45) NOT NULL,
PRIMARY KEY ('id')
)
ENGINE = InnoDB;
CREATE TABLE 'tagcloud'.'resourcetag' (
'id_resource' INTEGER UNSIGNED NOT NULL,
'id_tag' INTEGER UNSIGNED NOT NULL,
'weight' INTEGER UNSIGNED NOT NULL DEFAULT 1,
PRIMARY KEY ('id_resource', 'id_tag'),
CONSTRAINT 'FK_resourcetag_1' FOREIGN KEY 'FK_resourcetag_1' ('id_resource')
REFERENCES 'resource' ('id')
ON DELETE CASCADE
ON UPDATE CASCADE,
CONSTRAINT 'FK_resourcetag_2' FOREIGN KEY 'FK_resourcetag_2' ('id_tag')
REFERENCES 'tag' ('id')
ON DELETE CASCADE
ON UPDATE CASCADE
)
-- Insert some pictures
insert into resource (title, description) values ('Colosseo di Roma', 'Una foto del monumento romano piu famoso.');
insert into resource (title, description) values ('Duomo di milano', 'La nebbia rende la piazza magica.');
insert into resource (title, description) values ('Tramonti isola', 'Un tramonto dell isola di Malta.');
insert into resource (title, description) values ('Golfo di Napoli', 'Ecco come si vede il Vesuvio da Sorrento.');
insert into resource (title, description) values ('Calabria: mare', 'Vista panoramica della costa degli dei');
In fondo allo script anche alcune query per popolare la tabella.
Il passo successivo è quello di creare il gestore del database. Per prima cosa definiamo l'interfaccia in base a quelli che sono i nostri requisiti. Immaginiamo la presenza di un metodo per l'inserimento di una nuova risorsa, di un nuovo tag, di una nuova associazione, il caricamento filtrato per tag...
Listato 2. DBManager.java - Definiamo il comportamento (i metodi) necessari per gestire i casi d'uso
package it.html.tagcloud;
import java.util.Collection;
public interface DBManager {
//Effettua l'operazione di aggiunta della risorsa R al database
public void add(Resource r);
//Effettua l'operazione di aggiunta del tag T alla risorsa R
public void add(Resource r, Tag t);
//Effettua l'operazione di aggiunta del tag T al database
public void add(Tag t);
//Restituisce la risorsa relativa all'id passato come riferimento
public Resource loadResource(int id);
//Restituisce la risorsa relativa all'id passato come riferimento
public Tag loadTag(int id);
//Restituisce la risorsa relativa al nome passato come riferimento
public Tag loadTag(String name);
//Restituisce la collezione di tutti i tag
public Collection loadTags();
//Restituisce la collezione di tutte le risorse
public Collection loadResources();
//Restituisce la collezione di risorse relative al tag passato come riferimento
public Collection loadResources(Tag t);
//Verifica la presenza dell'associazione risorsa/tag R/T
public boolean exist(Resource r, Tag t);
//Aggiunge un voto all'associazione risorsa/tag
public void updateResourceTag(Resource r, Tag t);
}
Il commento in testa ad ogni metodo dovrebbe spiegare in maniera piuttosto chiara l'esigenza dello stesso metodo. D'altra parte non c'è nessuna particolare logica se non l'inserimento di un componente, il suo caricamento o la sua modifica.
L'implementazione concreta è disponibile nel file DBManagerConcrete.java presente nell'esempio scaricabile. La omettiamo perché esula dallo scopo dell'articolo e visto che in questa semplice implementazione, per non introdurre elementi di disturbo abbiamo individuato una semplice soluzione, con la gestione delle connessioni JDBC affidata alla stessa classe.
In ambiente di produzione reale sarebbe opportuno individuare soluzioni più adatte, come l'uso di Datasource o altro per gestire le connessioni dall'esterno dell'applicazione e poterle manipolare indipendentemente da essa. Comunque, come potete vedere dal link non si tratta ne più e ne meno dell'esecuzione di query SQL.
Controller e interfaccia web
Dopo aver realizzato il gestore del database (ed averlo testato a sufficienza per garantirne la funzionalità), procediamo con la logica di business della nostra web application. In un contesto java, come ormai abbiamo visto in altre circostanze, il controllo sarà affidato ad una servlet che fungerà da dispatcher e definirà il flusso di operazioni da eseguire per completare i casi d'uso definiti nei requisiti utente.
La servlet farà uso del DBManager per le operazioni di persistenza, mentre verrà affiancata da alcune pagine JSP (che vedremo avanti) per le operazioni di rappresentazione.
Listato 3. Il controller gestisce il flusso di esecuzione dei casi d'uso individuati
public class Controller extends HttpServlet {
private DBManager db;
public void init(){
try {
db=new DBManagerConcrete();
} catch (ClassNotFoundException ex) {
System.out.println("Classe driver non trovata. Inserire il driver sotto il classpath.");
}
}
//Metodo dispatcher
protected void service(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {
String op=request.getParameter("op");
if (op.equals("showAll"))
showResourceList(request,response);
else if (op.equals("showById"))
showResource(request,response);
else if(op.equals("showAllByTag"))
showResourceListByTag(request,response);
else if (op.equals("addTag"))
addTag(request,response);
}
..//
//Carica tutte le risorse del database relative al tag passato come riferimento
//su una collezione e la passa alla view per la sua visualizzazione
private void showResourceListByTag(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Recupero il tag passato come riferimento
String tagname=request.getParameter("tag");
//Carico il tag dal database
Tag tag=db.loadTag(tagname);
//Carico la lista
Collection toRet=db.loadResources(tag);
//Setto l'attributo di ritorno
request.setAttribute("list",toRet);
//effettuo l'operazione di forward
RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/list.jsp");
rd.forward(request,response);
}
//Carico il dettaglio della singola risorsa
private void showResource(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//Recupero il tag passato come riferimento
String id=request.getParameter("id");
//Carico la risorsa
Resource toRet=db.loadResource(Integer.parseInt(id));
//Setto l'attributo di ritorno
request.setAttribute("res",toRet);
//effettuo l'operazione di forward
RequestDispatcher rd=this.getServletContext().getRequestDispatcher("/detail.jsp");
rd.forward(request,response);
}
}
Il metodo init() serve per inizializzare il componente ed in questo caso per istanziare un nuovo DBManager: in questo caso creeremo la classe col costruttore di default, in un ambiente di sviluppo reale ci dovremmo preoccupare di mediare la connessione al database passando l'indirizzo del DB o meglio attraverso un Datasource.
Il metodo service()
è il dispatcher che si occupa di inoltrare la richiesta al metodo che definisce il caso d'uso. E vediamo proprio il primo caso d'uso, il metodo showResourceListByTag()
, che rappresenta il requisito "Visualizza lista risorse associate al tag". Come si vede bene dal codice, il flusso di operazioni è il caricamento del tag (a partire dal nome del tag passato in riferimento nella request), il caricamento della lista di risorse, a partire dal tag appena caricato, ed infine il forward verso la pagina list.jsp, passando come attributo la lista di risorse appena caricate.
Il secondo caso d'uso "Visualizza dettaglio risorsa" è realizzato dal metodo showResource()
che si occupa di recuperare l'identificativo passato come parametro, caricare la risorsa ed inoltrarla come attributo alla pagina detail.jsp.
Come vediamo il flusso è semplice e comprensibile: recupero dell'identificativo (un codice, una stringa, ecc), caricamento delle informazioni dal layer di persistenza, inoltro delle informazioni verso il layer di rappresentazione: in tre parole Model View Controller.
Ritorniamo sulla servlet e vediamo la realizzazione dell'ultimo requisito (Aggiungi tag a risorsa). Anche qui, dopo aver definito il flusso di operazioni da eseguire, le codifichiamo opportunamente con l'aiuto del layer di persistenza, mostrando un semplice messaggio di testo all'utente finale.
Listato 4. Aggiunge un tag alla risorsa indicati come riferimento
private void addTag(HttpServletRequest request, HttpServletResponse response) throws IOException {
//Recupero i parametri
String tagname=request.getParameter("tag");
String id=request.getParameter("id");
//carico le risorse dal database
Tag tag=db.loadTag(tagname);
Resource res=db.loadResource(Integer.parseInt(id));
//Flusso di esecuzione:
//Step 1
if(tag==null){
db.add(new Tag(tagname));
tag=db.loadTag(tagname);
}
//Step 2
if(!db.exist(res,tag))
db.add(res,tag);
//Step 3
else
db.updateResourceTag(res,tag);
response.getWriter().println("Il tag "+tag.getName()+" e' stato associato alla risorsa "+res.getTitle());
}
A questo punto in molti staranno pensando che ci siamo dimenticati dell'elemento principale, in quanto nella servlet manca un metodo da richiamare per visualizzare la tag cloud.
Considerando che una tag cloud può venire usata all'interno di una web application in diverse pagine e come elemento visuale della pagina (quindi non come una richiesta/risposta in base ad un input), abbiamo pensato che la migliore soluzione fosse quella di creare un custom tag. In questo modo gli diamo una precisa connotazione come componente visuale oltre che consentire una migliore manutenzione del singolo componente (rispetto l'intera applicazione).
Per la nostra esigenza il custom tag si aspetta due attributi, uno per definire il nome dello stile da utilizzare (cssName
), uno per gestire il numero di link (kind
) da visualizzare.
Listato 5. La classe contiene la logica di rappresentazione del custom tag
public class CloudTag extends TagSupport {
..//
//Metodo di inizio del tag
public int doStartTag() {
try {
JspWriter out = pageContext.getOut();
out.println("<div class=""+cssName+"">");
//Carico tutti i tag
Collection coll=db.loadTags();
Iterator it=coll.iterator();
..//
while(it.hasNext() && (limit==-1 || i<limit)){
Tag tmp=(Tag)it.next();
//la logica del tagging è su base logaritmica
int importance=tmp.getImportance();
int cssSpanType=(int)Math.round(Math.log10(importance));
//Stampiamo l'elemento con l'opportuna formattazione
out.println("<span class='cloudSpan"+cssSpanType+"'>");
out.println("<a href='controller?op=showAllByTag&tag="+tmp.getName()+"'>"+tmp.getName()+"</a>");
out.println("</span>");
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
return TagSupport.SKIP_BODY;
}
..//
}
Per semplicità vediamo il solo metodo di logica di rappresentazione (CloudTag.java nell'esempio allegato). Viene caricata una lista di tag, ed ogni tag viene inserito in un blocco <span>, il cui stile css è definito da cloudSpanXX
, dove XX è l'importanza del tag. Noi abbiamo deciso di utilizzare una scala logaritmica. Voi potrete definire la politica che meglio si adatta al sito (una scala logaritmica funziona bene su numeri molto grandi).
Omettiamo il commento del descrittore tld (file WEB-INF/cloudtag.tld nel download) e del foglio di stile (css/style.css) che potete approfondire scaricando l'esempio. Ora abbiamo tutto quello che ci serve, non ci rimane che l'interfaccia grafica.
Partiamo dalla pagina list.jsp, che si occupa di mostrare una collezione di oggetti Resource (che la servlet carica nell'attributo list).
Listato 6. List.jsp
<%@ page import="java.util.*,it.html.tagcloud.*" %>
<%@ taglib uri="/WEB-INF/cloudTag.tld" prefix="ct" %>
<html>
<head>
<title>Lista immagini</title>
<link href="style/style.css" rel="stylesheet">
</head>
<body>
<h1>Lista immagini</h1>
<hr/>
<p>
<ct:cloud cssName="cloudtag" kind="all"/>
<div>
<a href="controller?op=showAll">Mostra tutti</a>
</div>
</p>
<p class="list">
<%
//Iteriamo gli elementi presenti nell'attributo list
Collection coll=(Collection)request.getAttribute("list");
if (coll.isEmpty()){
out.println("<b>Nessuna risorsa presente</b>");
}else{
Iterator it=coll.iterator();
%>
<table>
<%
while(it.hasNext()){
Resource res=(Resource)it.next();
%>
<tr>
<td>
<a href="controller?op=showById&id=<%=res.getId()%>">
<strong><%=res.getTitle()%></strong>
</a>
</td>
<td>
<%=res.getDescription()%>
</td>
</tr>
<%
}
%>
</table>
<%
}
%>
</p>
</body>
</html>
Notiamo come a questo punto sia davvero facile visualizzare la nuvola di tag, utilizzando il descrittore xml <ct:cloud ...>. Il resto è una iterazione sugli elementi della collezione.
La pagina di dettaglio (detail.jsp) è quella che si occuperà di mostrare la risorsa all'utente e di permettergli di eseguire l'operazione di etichettatura.
Listato 7. Pagina di dettaglio
<%@ page import="java.util.*,it.html.tagcloud.*" %>
<%
Resource res=(Resource)request.getAttribute("res");
%>
<html>
<div class="leftPanel">
<img width="320" height="200" src="img/<%=res.getId()%>.jpg">
<hr/>
<p class="tags">
Tag: <%
Collection coll=res.getTags();
if (coll.isEmpty()){
out.println("Nessuno");
}else{
Iterator it=coll.iterator();
while(it.hasNext()){
Tag tag=(Tag)it.next();
%>
<span><a href="controller?op=showAllByTag&tag=<%=tag.getName()%>"><%=tag.getName()%></a></span>
<span>[<a href="controller?op=addTag&id=<%=res.getId()%>&tag=<%=tag.getName()%>">+</a>]</span>
<%
}
}
%>
</p>
<p>
<form method="post" action="controller">
<input type="hidden" name="op" value="addTag" />
<input type="hidden" name="id" value="<%=res.getId()%>" />
Aggiungi un tag: <input type="text" name="tag" size="20" />
<input type="submit" value="aggiungi" />
</form>
</p>
</div>
<div class="rightPanel">
<%=res.getDescription()%>
</div>
</body>
</html>
La pagina mostra le informazioni della risorsa, tra cui i tag associati ad essa. Ad ogni singolo tag associamo un link per la visualizzazione delle risorse di quel tag ed un link che permetta all'utente di associare un voto (richiamando la seguente funzione sul controller controller?op=addTag&id=<%=res.getId()%>&tag=<%=tag.getName()%>
). In questa maniera ogni utente può contribuire a dare un voto in più all'associazione risorsa-tag che è già presente.
Il form successivo peraltro dà la possibilità di aggiungere un nuovo tag, riutilizzando lo stesso caso d'uso di prima. Questa volta la costruzione della richiesta però deve passare dal form e dai suoi parametri (uno nascosto, l'id della risorsa) ed uno inserito dall'utente (il tag vero e proprio).
Esecuzione
Ora l'applicazione è pronta per essere eseguita e vediamo di seguito qualche immagine di esempio.
La pagina list.jsp, visualizza la nuvola di tag e successivamente il risultato di una ricerca su tutte le immagini presenti nel database. Clicchiamo sulla quarta immagine "Golfo di Napoli".
La pagina di dettaglio consente all'utente di aggiungere un tag, oppure di votare i tag già presenti (cliccando sul simbolo + accanto ad ogni tag). Dopo aver effettuato diverse operazioni, clicchiamo sul tag "mare" per visualizzare tutte le risorse associate a questo tag.
Vengono visualizzate così tutte le foto che hanno associato il tag "mare".