PluginManager.java
/*
* GovWay - A customizable API Gateway
* https://govway.org
*
* Copyright (c) 2005-2024 Link.it srl (https://link.it).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3, as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openspcoop2.monitor.engine.dynamic;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.openspcoop2.core.commons.CoreException;
import org.openspcoop2.core.config.RegistroPlugin;
import org.openspcoop2.core.config.RegistroPlugins;
import org.openspcoop2.core.config.constants.StatoFunzionalita;
import org.openspcoop2.generic_project.exception.NotFoundException;
import org.openspcoop2.core.plugins.constants.TipoPlugin;
import org.openspcoop2.utils.UtilsException;
import org.openspcoop2.utils.date.DateManager;
import org.slf4j.Logger;
/**
* PluginManager
*
* @author Poli Andrea (apoli@link.it)
* @author $Author$
* @version $Rev$, $Date$
*/
public class PluginManager {
private IRegistroPluginsReader registroPluginsReader;
private Date expireDate;
private int expireSeconds;
private final org.openspcoop2.utils.Semaphore lockExpire = new org.openspcoop2.utils.Semaphore("PluginManager-expire");
private PluginsImage pluginsImage = new PluginsImage();
private PluginsImage pluginsImageSwitchOld = null;
private final org.openspcoop2.utils.Semaphore lockImage = new org.openspcoop2.utils.Semaphore("PluginManager-image");
protected PluginManager(IRegistroPluginsReader registroPluginsReader, int expireSeconds) {
this.registroPluginsReader = registroPluginsReader;
this.expireSeconds = expireSeconds;
}
private void checkUpdate(Logger log) {
try {
Date nowDate = DateManager.getDate();
if(this.expireDate==null || nowDate.after(this.expireDate)) {
this.update(log, nowDate);
}
}catch(Exception t) {
log.error("Update plugin image failed: "+t.getMessage(),t);
}
}
public void updateFromConsoleConfig(Logger log) {
try {
if(this.expireDate!=null) {
Date expired = new Date(this.expireDate.getTime()+1);
this.update(log, expired);
}
}catch(Exception t) {
log.error("Update plugin image failed: "+t.getMessage(),t);
}
}
private void update(Logger log, Date nowDate) throws UtilsException, CoreException {
boolean update = false;
this.lockExpire.acquire("updateExpireDate");
try {
if(this.expireDate==null || nowDate.after(this.expireDate)) {
this.expireDate = new Date(nowDate.getTime()+(this.expireSeconds*1000));
update = true;
// l'aggiornamento effettivo lo faccio fuori dal synchronized per non bloccare le chiamate a _findClass, intanto che l'immagine viene aggiornata
// gli altri thread che entrano in questo metodo trovano la lor nowDate inferiore ad expireDate
}
}finally {
this.lockExpire.release("updateExpireDate");
}
if(update) {
RegistroPlugins registro = null;
try {
registro = this.registroPluginsReader.getRegistroPlugins();
}catch (NotFoundException notFound) {
log.debug(notFound.getMessage(),notFound);
}
this.update(log, registro);
}
}
private void update(Logger log, RegistroPlugins pluginsParam) throws UtilsException {
this.lockImage.acquire("update");
try {
RegistroPlugins plugins = null;
if(pluginsParam!=null) {
plugins = pluginsParam;
}
else {
plugins = new RegistroPlugins();
}
// verifico prima quelli da eliminare
List<String> pluginDaEliminare = new ArrayList<>();
if(this.pluginsImage!=null && !this.pluginsImage.plugins.isEmpty()) {
for (String pluginName : this.pluginsImage.plugins.keySet()) {
boolean found = false;
if(plugins.sizePluginList()>0) {
for (RegistroPlugin pluginNew : plugins.getPluginList()) {
if(pluginNew.getNome().equals(pluginName)) {
found = true;
break;
}
}
}
if(!found) {
pluginDaEliminare.add(pluginName);
}
}
}
// Nuova immagine
PluginsImage newImage = null;
if(plugins.sizePluginList()>0) {
newImage = new PluginsImage();
HashMap<String, String> mapPosizioniToNomi = new HashMap<>();
for (RegistroPlugin pluginNew : plugins.getPluginList()) {
if(!StatoFunzionalita.ABILITATO.equals(pluginNew.getStato())) {
continue;
}
if(pluginNew.sizeArchivioList()<=0) {
continue; // vuoto
}
String posPad = StringUtils.leftPad(pluginNew.getPosizione()+"", 10);
mapPosizioniToNomi.put(posPad, pluginNew.getNome());
// check se esiste
if(this.pluginsImage!=null && this.pluginsImage.plugins.containsKey(pluginNew.getNome())) {
// check se e' stato aggiornato
Plugin active = this.pluginsImage.plugins.get(pluginNew.getNome());
if(pluginNew.getData().after(active.getDate())) {
// Da aggiornare
pluginDaEliminare.add(pluginNew.getNome()); // elimino nella vecchia immagine
Plugin pluginNewInstance = null;
try {
pluginNewInstance = new Plugin(pluginNew);
}catch(Exception e) {
log.error("Errore durante l'istanziazione del plugin '"+pluginNew.getNome()+"': "+e.getMessage(),e);
}
if(pluginNewInstance!=null) {
newImage.plugins.put(pluginNew.getNome(), pluginNewInstance);
}
}
else {
// non modificato
newImage.plugins.put(pluginNew.getNome(), active);
}
}
else {
Plugin pluginNewInstance = null;
try {
pluginNewInstance = new Plugin(pluginNew);
}catch(Exception e) {
log.error("Errore durante l'istanziazione del plugin '"+pluginNew.getNome()+"': "+e.getMessage(),e);
}
if(pluginNewInstance!=null) {
newImage.plugins.put(pluginNew.getNome(), pluginNewInstance);
}
}
}
// ordino per posizione
if(!mapPosizioniToNomi.isEmpty()) {
List<String> posizioni = new ArrayList<>();
posizioni.addAll(mapPosizioniToNomi.keySet());
Collections.sort(posizioni);
for (String pos : posizioni) {
newImage.pluginsActiveOrdered.add(mapPosizioniToNomi.get(pos));
}
}
}
// effettuo switch
this.pluginsImageSwitchOld = this.pluginsImage;
this.pluginsImage = newImage;
// Effettuo la chiusura di quelli da eliminare
if(!pluginDaEliminare.isEmpty()) {
for (String pluginName : pluginDaEliminare) {
try {
this.pluginsImageSwitchOld.plugins.get(pluginName).close();
}catch(Exception t) {
// ignore
}
}
}
this.pluginsImageSwitchOld = null;
}finally {
this.lockImage.release("update");
}
}
public void close() {
// Chiusura di tutti
this.lockImage.acquireThrowRuntime("close");
try {
if(this.pluginsImage!=null && this.pluginsImage.plugins.size()>0) {
for (Plugin plugin : this.pluginsImage.plugins.values()) {
try {
plugin.close();
}catch(Exception t) {
// close
}
}
}
if(this.pluginsImageSwitchOld!=null &&
this.pluginsImageSwitchOld.plugins.size()>0) {
for (Plugin plugin : this.pluginsImageSwitchOld.plugins.values()) {
try {
plugin.close();
}catch(Exception t) {
// close
}
}
}
}finally {
this.lockImage.release("close");
}
}
public Class<?> findClass(Logger log, TipoPlugin tipoClasseDaRicercare, String className) throws ClassNotFoundException {
return this.findClassEngine(log, tipoClasseDaRicercare, null, className, true);
}
public Class<?> findClass(Logger log, TipoPlugin tipoClasseDaRicercare, String className, boolean searchDefaultClassLoader) throws ClassNotFoundException {
return this.findClassEngine(log, tipoClasseDaRicercare, null, className, searchDefaultClassLoader);
}
public Class<?> findClass(Logger log, String tipoClasseDaRicercare, String className) throws ClassNotFoundException {
return this.findClassEngine(log, null, tipoClasseDaRicercare, className, true);
}
public Class<?> findClass(Logger log, String tipoClasseDaRicercare, String className, boolean searchDefaultClassLoader) throws ClassNotFoundException {
return this.findClassEngine(log, null, tipoClasseDaRicercare, className, searchDefaultClassLoader);
}
private Class<?> findClassEngine(Logger log, TipoPlugin tipoClasseDaRicercare,String tipoClasseCustomDaRicercare, String className, boolean searchDefaultClassLoader) throws ClassNotFoundException {
checkUpdate(log);
// Se server gestire tramite una cache
// Se abilitato prima cerco sempre nel classloader attuale.
// Il classloader dinamico verrà utilizzato SOLAMENTE se il tipo non viene risolto prima tramite i meccanismi standard.
// Questo behaviour permette di avere poi un synchronized sul semaforo del classloader dinamico solamente quando serve davvero,
// visto che il caricamento dinamico delle classi è molto diffuso all'interno dell'architettura di GovWay.
ClassNotFoundException notFound = null;
if(searchDefaultClassLoader) {
try {
return Class.forName(className);
}catch(ClassNotFoundException e) {
notFound = e;
}
}
if(this.pluginsImage!=null) { // potrebbe essere disabilitato
PluginsImage image = this.pluginsImage; // lo assegno, in modo che se avviene un update, cambia il riferimento
if(!image.pluginsActiveOrdered.isEmpty()) {
Class<?> c = findClassEngine(image, tipoClasseDaRicercare, tipoClasseCustomDaRicercare, className);
if(c!=null) {
return c;
}
}
}
if(notFound!=null) {
throw notFound;
}
return null;
}
private Class<?> findClassEngine(PluginsImage image, TipoPlugin tipoClasseDaRicercare, String tipoClasseCustomDaRicercare, String className){
List<String> listPluginsActiveOrdered = image.pluginsActiveOrdered;
for (String pluginName : listPluginsActiveOrdered) {
Plugin plugin = image.plugins.get(pluginName);
if(plugin!=null) {
Class<?> c = findClassEngineByPlugin(plugin, tipoClasseDaRicercare, tipoClasseCustomDaRicercare, className);
if(c!=null) {
return c;
}
}
}
return null;
}
private Class<?> findClassEngineByPlugin(Plugin plugin, TipoPlugin tipoClasseDaRicercare, String tipoClasseCustomDaRicercare, String className){
ClassLoader classLoader = null;
if(tipoClasseDaRicercare!=null) {
classLoader = plugin.getClassLoader(tipoClasseDaRicercare);
}else {
classLoader = plugin.getClassLoader(tipoClasseCustomDaRicercare);
}
if(classLoader!=null) {
Class<?> c = null;
try {
c = classLoader.loadClass(className);
}catch(ClassNotFoundException cNotFound) {
// ignore
}
if(c!=null) {
return c;
}
}
return null;
}
}
class PluginsImage {
HashMap<String, Plugin> plugins = new HashMap<>();
List<String> pluginsActiveOrdered = new ArrayList<>();
}