Buongiorno a tutti!
Eccomi di nuovo da voi per un post su Liferay 7.1 e le magie di OSGi!
Anche oggi vorrei raccontarvi quello che i miei tanti animali mitologici mi hanno chiesto qualche tempo fa e come sono riuscito a risolvere il problema!
Per farlo, però, come sempre vi devo raccontare il caso d'uso, così che l'implementazione possa essere quantomeno sensata.. :)
Caso d'uso: codice centralizzato ma.. Specializzato per ogni modulo!
Lo so: la cosa più assurda dei miei post sono i requisiti che i miei animali mitologici riescono a darmi! :)
Però credetemi: quando me li danno me li motivano e, almeno sulla carta, sembra che abbiano pure un senso!
Nello specifico, questa volta, quello che mi hanno chiesto è stato proprio.. Creare del codice che dovrà essere centralizzato (per ovvie ragione) ma che.. Debba essere specializzato per ogni modulo! Il motivo è semplice:
- centralizzato: perché alla fine la logica è uguale in tutti i punti dove viene utilizzato;
- specializzato: perché vogliono predisporsi al cambiamento; nel caso la logica standard non vada più bene in un modulo, vogliono semplicemente cambiarne l'implementazione in quel modulo e tutto deve continuare a funzionare.
Come al solito, nemmeno a farlo apposta, tutti a me capitano.. :D
Ora che abbiamo definito che i miei animali mitologici sono particolari :), andiamo a vedere come ho risolto questi due vincoli, sfruttando una delle caratteristiche di OSGi che usate spesso ma alla quale, magari, non avete mai prestato particolare attenzione..
Coding time!
Partiamo subito facendo vedere il codice che ho centralizzato; qui, ovviamente, non mi soffermo sul cosa, perché è abbastanza indifferente. Mi concentro di più sul come, che è la parte un po' più interessante dell'implementazione.
package it.dvel.playground.search;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.filter.BooleanFilter;
import com.liferay.portal.kernel.util.Validator;
import com.liferay.portal.search.spi.model.query.contributor.ModelPreFilterContributor;
import com.liferay.portal.search.spi.model.registrar.ModelSearchSettings;
import it.dvel.playground.util.search.SearchContextConstants;
import org.osgi.service.component.annotations.Component;
import java.util.Date;
@Component(
immediate = true,
property = "model.pre.filter.contributor.id=DateRangeFilter",
service = ModelPreFilterContributor.class
)
public class DateRangePreFilterContributor extends BaseDateRangePreFilterContributor {
@Override
public void contribute(BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings,
SearchContext searchContext) {
Date startDate = (Date) searchContext.getAttribute(SearchContextConstants.SEARCH_BY_FROM_DATE);
Date endDate = (Date) searchContext.getAttribute(SearchContextConstants.SEARCH_BY_TO_DATE);
if (Validator.isNotNull(startDate) && Validator.isNotNull(endDate)) {
endDate = setTime(endDate, 23, 59);
if (_log.isDebugEnabled()) {
_log.debug("Start Date: " + startDate);
_log.debug("End Date: " + endDate);
}
String field = (String) searchContext.getAttribute("it.dvel.playground.search.common.field.date.range");
addFilter(field, booleanFilter, startDate, endDate);
}
}
private static final Log _log = LogFactoryUtil.getLog(DateRangePreFilterContributor.class);
}
Vi manca metà del mondo degli oggetti della modellazione, però avete l'unico che conta: l'oggetto che definisco centralizzato! Questo oggetto, che nel dettaglio serve a scatenare un filtro sulla ricerca attraverso l'indice nel caso siano presenti due date (lasciate stare il perché, dai.. :)), ha una particolarità: definisce un suo ID attraverso una property.
Questa è la riga incriminata:
property = "model.pre.filter.contributor.id=DateRangeFilter",
Questa è la caratteristica interessante, e presto vedremo il perché!
Injection di oggetti con.. Filtro!
Il secondo requisito è quello della specializzazione: l'idea di base dei miei animali mitologici era quella di essere pronti al cambiamento! Siccome però, per definizione, gli animali mitologici sono come vogliamo vederli noi :), i miei erano pigri (almeno quanto me :)) e quindi volevano a tutti i costi che ogni modulo specializzato avesse:
- una sua classe specifica per questo tipo di filtro;
- che la classe specifica facesse allegramente uso dell'implementazione centralizzata.
So che per i più questo, nel mondo del software ad oggetti, si chiama banalmente proxy
, però qui la cosa si fa un po' più complicata, perché parliamo di Servizi rilasciati attraverso bundle
differenti nel container OSGi.
A questo punto.. Come fare a risolvere il requisito?
Bhe, come dice il titolo di questo capitolo (e spoilerato poi anche dal dettaglio del capitolo precedente.. ;)), ci basterà solamente filtrare gli oggetti che ci vengono iniettati e il gioco sarà fatto!
Ecco quindi come ho implementato l'oggetto all'interno del modulo specializzato, usando chiaramente il concetto di proxy
sull'oggetto centralizzato!
package it.dvel.playground.avvisi.search;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import com.liferay.portal.kernel.search.SearchContext;
import com.liferay.portal.kernel.search.filter.BooleanFilter;
import com.liferay.portal.search.spi.model.query.contributor.ModelPreFilterContributor;
import com.liferay.portal.search.spi.model.registrar.ModelSearchSettings;
import it.dvel.playground.util.search.FieldConstants;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(
immediate = true,
property = "indexer.class.name=it.dvel.playground.avvisi.model.Avviso",
service = ModelPreFilterContributor.class
)
public class AvvisiDateRangePreFilterContributor implements ModelPreFilterContributor {
@Override
public void contribute(BooleanFilter booleanFilter, ModelSearchSettings modelSearchSettings,
SearchContext searchContext) {
searchContext.setAttribute("it.dvel.plaground.search.common.field.date.range", FieldConstants.AVVISO_DATA_REDAZIONE);
dateRangeFilterPreFilterContributor.contribute(booleanFilter, modelSearchSettings, searchContext);
}
@Reference(target = "(model.pre.filter.contributor.id=DateRangeFilter)")
protected ModelPreFilterContributor dateRangeFilterPreFilterContributor;
private static final Log _log = LogFactoryUtil.getLog(AvvisiDateRangePreFilterContributor.class);
}
La magia è tutta qui:
@Reference(target = "(model.pre.filter.contributor.id=DateRangeFilter)")
protected ModelPreFilterContributor dateRangeFilterPreFilterContributor;
Nella Reference
, infatti, io specifico indirettamente che vorrei un oggetto di tipo ModelPreFilterContributor
però sono selettivo: non mi accontento di averne uno a caso ma voglio proprio quello che mi pare a me, quello che ha ID che vale DateRangeFilter
.
Ed è il container OSGi a fare la magia, popolandomi il field interno con l'oggetto specifico!
Ovviamente questo tipo di implementazione sfrutta la caratteristica dell'iniezione selettiva degli oggetti fatta dal container OSGi, però, chiaramente, è basata su un assunto banale, ovvero che esista un solo ModelPreFilterContributor
che ha quell'ID.
Questo è un assunto molto forte, però è funzionale all'esempio che volevo mostrarvi!
Adesso la palla è nel vostro campetto: se qualcuno ha idea di come rendere più robusta l'implementazione può scriverlo nei commenti, così da confrontarci e crescere tutti insieme, ovviamente migliorandoci! :)
Fino ad allora, come al solito, divertitevi e fatemi sapere se vi torna tutto quello che ho scritto!
Se avete domande o dubbi, potete scriverli nei commenti e sarò / saremo ben lieti di aiutarvi!!
Buon divertimento a tutti con OSGi, Liferay 7.1 e i filtri sulle iniezioni degli oggetti! :)
Materiale utile alla discussione
- Javadoc dell'implementazione di Filter nel core di OSGi
- La RFC1960 che rappresenta come i filtri di LDAP vadano costruiti (sintassi e logica): comodo perché è da qui che sono derivati i filtri sugli oggetti nel container OSGi