In un abbiamo visto da vicino cosa significa “risorsa” per JSF 2, insieme alle convenzioni e agli strumenti necessari per gestirle. Una volta messo in piedi questo sistema, una domanda lecita è: ma come faccio a gestire il loro versionamento? Se modifico un css o uno script, come faccio ad essere sicuro che il browser dell’utente scarichi l’ultima versione e invalidi quella in cache? Rispondiamo a questa domanda in stile JSF 2!
Lo scopo
Normalmente si vede nello sviluppo web che i link a un foglio di stile o ad un JavaScript terminano con un query param del tipo:
Ovviamente file statici come .css e .js non possono ricevere parametri. Allora a che serve? Immaginiamo la cache del browser come una mappa, la cui chiave è l’URL della risorsa e il valore è la risorsa stessa. In questo caso la chiave sarà rispettivamente:
- my-style.css?v=1.0
- my-script.js?v=1.0
Se cambio la versione delle risorse sul server, è facile capire che il loro URL cambierà, invalidando automaticamente le versioni presenti nei browser! Questo sistema sembra artigianale, ma in realtà è molto efficace! Vediamo come lo ha inglobato JSF 2.
Il sistema di versionamento secondo JSF 2
JSF 2 ha adottato lo stesso meccanismo di versionamento delle risorse: una volta introdotto, gli URL delle risorse sul client termineranno tutti come mostrato poco fa. La gestione però è automatica, non dobbiamo preoccuparci cioè di modificare le dichiarazioni dei tag outputStylesheet o outputScript, bensì di come sono organizzate le risorse sul file system!!
Prendiamo per esempio un header di pagina tale che:
Esistono due approcci per organizzare le versioni, a fronte dello stesso codice html:
Versionamento per libreria
Organizziamo le risorse come in figura:
cioè, ogni libreria è formata da sottocartelle con nome numerico che ne rappresenta la versione. Le eventuali sottoversioni sono separate da underscore (_) (non dal punto mi raccomando!). Il ResourceHandler, automaticamente caricherà la versione più aggiornata per ogni libreria, ovvero:
- la 1_0_1 per i css;
- la 1_3 per i js.
Sul client, otterremo una cosa del tipo:
Vantaggi? Ho organizzato le risorse per versione e nella pagina non devo modificare niente!
Svantaggi? Ogni file posto al di fuori delle cartelle “numerate” (cioè figlio diretto di css o js) non viene trovato.
Versionamento per singola risorsa
Se non vogliamo gestire un sistema di versionamento per libreria, è possibile farlo per singola risorsa, con una struttura alquanto bizzarra. Ammettiamo di voler controllare solo le versioni di stile_a.css: dovremo creare una cartella con questo nome e inserire i file css al suo interno nominati con la notazione delle versioni, come in figura:
In pratica è la modalità precedente invertita: il nome del file css diventa quello della cartella e il numero delle versione, che prima era una cartella, adesso diventa il nome del file css.
Vantaggi? Dato che il versionamento è a livello di file, possono coesistere file versionati da quelli non versionati all’interno della stessa libreria (in questo caso, style.css viene risolto).
Svantaggi? Se proviamo ad usare entrambi i sistemi di versionamento, vince quello per libreria.
La via alternativa
Il sistema di versionamento per file può essere molto interessante quando importiamo nel nostro progetto stili o script di terze parti, la cui versione non è controllata da noi. Per quanto riguarda invece i nostri stili o script, creare file o cartelle diverse per ogni versione può essere un problema. Se infatti il nostro progetto è controllato da un Version Control System (CVS/SVN/GIT per intendersi), creare un nuovo file ad ogni versione frammenta la storia di quello stile o di quello script in più file, per cui tracciare i cambiamenti a ritroso, in caso di necessità, può essere fastidioso perché è necessario saltare tra più file.
Fortunatamente JSF 2 è molto flessibile, e ci permette di personalizzare il ResourceHandler come ci pare e piace. Torniamo quindi alle basi: vogliamo che venga aggiunta la versione ai link ai file, però senza impazzire con cartelle, sottocartelle o nomi numerici. Prendiamo spunto da e scriviamo il nostro ResourceHandler:
public class VersionedResourceHandler extends ResourceHandlerWrapper {
private ResourceHandler wrapped;
public VersionedResourceHandler(ResourceHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public Resource createResource(String resourceName, String libraryName) {
Resource resource = super.createResource(resourceName, libraryName);
if (resource != null && libraryName != null && !libraryName.equalsIgnoreCase("javax.faces"))
return new VersionedResource(resource);
else
return resource;
}
@Override
public ResourceHandler getWrapped() {
return wrapped;
}
}
Questa classe ci permette di decorare l’oggetto Resource con il nostro VersionedResource, che aggiungerà il query param v con la versione della nostra applicazione:
public class VersionedResource extends ResourceWrapper implements Serializable {
private Resource wrapped;
public VersionedResource(Resource wrapped) {
this.wrapped = wrapped;
}
@Override
public Resource getWrapped() {
return wrapped;
}
@Override
public String getRequestPath() {
String version = FacesContext.getCurrentInstance().
getExternalContext().getInitParameter("myApp.VERSION");
return super.getRequestPath() + "&v=" + version;
}
@Override
public String getContentType() {
return getWrapped().getContentType();
}
@Override
public String getLibraryName() {
return getWrapped().getLibraryName();
}
@Override
public String getResourceName() {
return getWrapped().getResourceName();
}
@Override
public void setContentType(String contentType) {
getWrapped().setContentType(contentType);
}
@Override
public void setLibraryName(String libraryName) {
getWrapped().setLibraryName(libraryName);
}
@Override
public void setResourceName(String resourceName) {
getWrapped().setResourceName(resourceName);
}
@Override
public String toString() {
return getWrapped().toString();
}
}
La versione, unica per tutte le risorse, è definita nel web.xml:
myApp.VERSION 1.0
Infine, è necessario registrare il ResourceHandler nel faces-config.xml:
it.cosenonjaviste.jsf.resources.VersionedResourceHandler
Conclusioni
JSF 2 quindi, oltre ad una gestione centralizzata delle risorse, offre anche un sistema di versionamento legato ad esse, che comunque è possibile personalizzare o rifare a piacimento come abbiamo visto nell’ultimo esempio. Personalmente, tra i due metodi offerti da JSF, preferisco il secondo (quello del versionamento per file) perché almeno non si è costretti a “versionare” tutti i file di una libreria. Alla fine forse la soluzione ripresa da PrimeFaces è quella più semplice e più funzionale… lascio a voi la decisione!