Ok, ammetto che questa è una casistica molto particolare che difficilmente vi troverete a dover risolvere; a me però è capitato quindi vi risparmio un pò di fatica.
In pratica il problema è quello di riuscire ad invocare, da un vostro plugin custom (che chiamerò plugin interno), il metodo di una classe Java presente in un altro plugin custom (che chiamerò plugin esterno) quindi appartenente ad un differente classloader.
Normalmente questa cosa non sarebbe possibile perchè Liferay possiede diversi classloader (uno globale, uno di portale ed uno per ogni plugin) con cui mantiene disaccoppiate classi e librerie; tuttavia mette allo stesso tempo a disposizione numerose classi di utility per "giocare" con i vari classloader.
Vediamo quindi come risolvere il nostro problema di classloading tra plugin.
Come prima cosa bisognerebbe ottenere un riferimento al classloader del plugin "esterno" e successivamente caricare la classe; per fortuna Liferay ci mette a disposizione una classe di utility che fa già tutto il lavoro sporco:
Class<?> clazz =
ClassResolverUtil.resolveByPortletClassLoader(className, servletContextName);
I 2 parametri hanno il seguente significato:
className
, è il nome della classe da caricare (completo di package ovviamente);servletContextName
, è il nome del contesto del plugin "esterno" ossia il nome del progetto Eclipse (ad es. book-store-portlet
).
Una volta ottenuta la classe del plugin "esterno", con un pò di reflection andiamo a recuperare il metodo da invocare:
Method method =clazz.getMethod("nomeMetodo", elenco_dei_parametri...);
Infine, sempre con un pò di reflection, possiamo invocare il metodo della classe:
List<MyEntity> items =
(List<MyEntity>) method.invoke(clazz.newInstance(), valore_dei_parametri...);
Normalmente potreste avere già finito, ma io ho avevo un altro problema, più stimolante: il metodo del plugin "esterno" mi doveva restituireun elenco di entità definite nel service.xml
del plugin "interno".
All'atto pratico quello che succede è che la chiamata method.invoke
restituisce sì un elenco ma di oggetti MyEntityClp
, appartenenti ad un classloader diverso da quello del plugin "interno" e quindi ogni tentativo di utilizzo fallisce con un ClassCastException
.
Come fare quindi? L'unica soluzione (o almeno l'unica che ho trovato) è quella di ricreare la lista di oggetti localmente al plugin "interno" nel modo seguente:
for (int i = 0; i < items.size(); i++) {
BaseModel<MyEntity> oldEntity = (BaseModel<MyEntity>) items.get(i);
MyEntity newEntity = myEntityPersistence.create(0);
newEntity.setModelAttributes(oldEntity.getModelAttributes());
items.set(i, newEntity);
}
Fate attenzione solo ad una cosa: l'entità restituita dal plugin "esterno" va castata a BaseModel<MyEntity>
e non direttamente a MyEntity
altrimenti incapperete in un bel ClassCastException
.
Buon anno!