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!