ConfigManager.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.core.mvc.properties.utils;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.openspcoop2.core.commons.CoreException;
import org.openspcoop2.core.mvc.properties.Compatibility;
import org.openspcoop2.core.mvc.properties.Config;
import org.openspcoop2.core.mvc.properties.Tags;
import org.openspcoop2.core.mvc.properties.utils.serializer.JaxbDeserializer;
import org.openspcoop2.generic_project.exception.DeserializerException;
import org.openspcoop2.utils.xml.AbstractValidatoreXSD;
import org.slf4j.Logger;

/**     
 * Manager delle configurazioni disponibili.
 *
 * @author Pintori Giuliano (pintori@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class ConfigManager {

	private static ConfigManager instance = null;
	private Logger log = null;
	private Map<String, Map<String,Config>> mapConfigBuildIn = null;
	private Map<String, Map<String,Config>> mapConfigFileSystem = null;
	private Map<String, Map<String,Config>> mapConfig = null;
	private AbstractValidatoreXSD validator = null; 
	
	public static ConfigManager getinstance(Logger log) throws CoreException {
		if(instance == null) {
			// spotbugs warning 'SING_SINGLETON_GETTER_NOT_SYNCHRONIZED'
			synchronized (ConfigManager.class) {
				init(log);
			}
		}
		return instance;
	}
	
	private static synchronized void init(Logger log) throws CoreException{
		instance = new ConfigManager(log);
	}
	
	
	private ConfigManager(Logger log) throws CoreException  {
		this.log = log;
		this.mapConfigBuildIn = new HashMap<>();
		this.mapConfigFileSystem = new HashMap<>();
		this.mapConfig = new HashMap<>();
		
		try {
			this.validator = XSDValidator.getXSDValidator(log);
		}catch(Exception e) {
			doError("Errore durante la init del ManagerConfigurazioni",e);
		}
	}
	private void doError(String msg,Exception e) throws CoreException {
		String msgError = msg +": "+ e.getMessage();
		this.log.error(msgError,e);
		throw new CoreException(e.getMessage(),e);
	}
	
	
	public void leggiConfigurazioni(PropertiesSourceConfiguration propertiesSourceConfiguration, boolean validazioneXSD) throws CoreException, DeserializerException{
		// Configurazioni builtIn
		if(!this.mapConfigBuildIn.containsKey(propertiesSourceConfiguration.getId()) || propertiesSourceConfiguration.isUpdateBuiltIn()) {
			if(this.mapConfigBuildIn.containsKey(propertiesSourceConfiguration.getId())) {
				this.mapConfigBuildIn.remove(propertiesSourceConfiguration.getId());
			}
			
			Map<String,Config> mapConfigFromDir = null;
			
			List<byte[]> builtIn = propertiesSourceConfiguration.getBuiltIn();
			
			if(builtIn != null && !builtIn.isEmpty()) {
				mapConfigFromDir = new HashMap<>();
				JaxbDeserializer xmlReader = new JaxbDeserializer();
				for (int i = 0 ; i < builtIn.size(); i++ ) {
					byte[] f = builtIn.get(i);
					// validazione XSD se prevista
					if(validazioneXSD) {
						try {
							this.validator.valida(new ByteArrayInputStream(f));
						}catch(Exception e) {
							doError("La configurazione builtIn numero ["+(i+1)+"] non e' valida",e);
						}
					}
					
					Config configDaFile = xmlReader.readConfig(f);
					String id = configDaFile.getId();
					if(mapConfigFromDir.containsKey(id))
						throw new CoreException("La configurazione builtIn con id '"+id+"' risulta duplicata all'interno di quelle precaricate nel sistema.");
						
					mapConfigFromDir.put(id,configDaFile);
				}
				this.mapConfigBuildIn.put(propertiesSourceConfiguration.getId(), mapConfigFromDir);
			}
		}
		
		// configurazioni da file system
		if(propertiesSourceConfiguration.getDirectory() != null && (!this.mapConfigFileSystem.containsKey(propertiesSourceConfiguration.getId()) || propertiesSourceConfiguration.isUpdate())) {
			if(this.mapConfigFileSystem.containsKey(propertiesSourceConfiguration.getId())) {
				this.mapConfigFileSystem.remove(propertiesSourceConfiguration.getId());
			}
			
			Map<String,Config> mapConfigFromDir = null;
			
			File dir = new File(propertiesSourceConfiguration.getDirectory());
			
			if(!dir.exists())
				throw new CoreException("Il path indicato ["+propertiesSourceConfiguration.getDirectory()+"] non esiste, impossibile leggere le configurazioni");
			
			if(!dir.isDirectory())
				throw new CoreException("Il path indicato ["+propertiesSourceConfiguration.getDirectory()+"] non e' una directory");
			
			String[] fileList = dir.list();
			
			if(fileList != null && fileList.length > 0) {
				mapConfigFromDir = new HashMap<>();
				JaxbDeserializer xmlReader = new JaxbDeserializer();
				for (String f : fileList) {
					File fileConfig = new File(dir.getPath() + File.separator + f); 
					if(!fileConfig.isDirectory()) {
						// validazione XSD se prevista
						if(validazioneXSD) {
							try {
								this.validator.valida(fileConfig);
							}catch(Exception e) {
								doError("La configurazione ["+fileConfig.getName()+"] non e' valida",e);
							}
						}
						
						Config configDaFile = xmlReader.readConfig(fileConfig);
						String id = configDaFile.getId();
						if(mapConfigFromDir.containsKey(id))
							throw new CoreException("La configurazione con id '"+id+"' risulta duplicata all'interno del path indicato ["+propertiesSourceConfiguration.getDirectory()+"].");
							
						mapConfigFromDir.put(id,configDaFile);
					}
				}
				this.mapConfigFileSystem.put(propertiesSourceConfiguration.getId(), mapConfigFromDir);
			}
		}
		
		// merge configurazioni
		if(!this.mapConfig.containsKey(propertiesSourceConfiguration.getId()) || propertiesSourceConfiguration.isUpdateBuiltIn() || propertiesSourceConfiguration.isUpdate()) {
			if(this.mapConfig.containsKey(propertiesSourceConfiguration.getId())) {
				this.mapConfig.remove(propertiesSourceConfiguration.getId());
			}
			
			Map<String, Config> mapBuiltIn = this.mapConfigBuildIn.get(propertiesSourceConfiguration.getId());
			Map<String, Config> mapFileSystem = this.mapConfigFileSystem.get(propertiesSourceConfiguration.getId());
			
			if(mapBuiltIn != null && mapBuiltIn.size() > 0 && mapFileSystem != null && mapFileSystem.size() > 0 ) {
				// controllo duplicati 
				for (String keyBuiltIn : mapBuiltIn.keySet()) {
					if(mapFileSystem.keySet().contains(keyBuiltIn))
						throw new CoreException("La configurazione con id '"+keyBuiltIn+"' risulta duplicata, e' presente sia come configurazione builtIn che come configurazione esterna, eliminare una delle due.");
				}
			}
			
			Map<String, Config> map = new HashMap<>();
			if(mapBuiltIn != null && mapBuiltIn.size() > 0)
				map.putAll(mapBuiltIn);
			if(mapFileSystem != null && mapFileSystem.size() > 0)
				map.putAll(mapFileSystem);
			
			this.mapConfig.put(propertiesSourceConfiguration.getId(), map);
		}
	}
	
	public List<String> getNomiConfigurazioni(PropertiesSourceConfiguration propertiesSourceConfiguration, String ... tags) {
		List<String> mapSortedKeys = new ArrayList<>();
		Map<String, List<String>> mapSorted = new HashMap<>();
				
		Map<String, Config> map = this.mapConfig.get(propertiesSourceConfiguration.getId());

		Iterator<String> iterator = map.keySet().iterator();
		while (iterator.hasNext()) {
			String id = iterator.next();
			
			Config config = map.get(id);
			if(tags!=null && tags.length>0) {
				Compatibility compatibility = config.getCompatibility();
				// se non e' compatibile con i tag richiesti non lo aggiungo alla lista
				if(!checkCompatibility(compatibility, Arrays.asList(tags))) 
					continue;
			}
			
			String labelId = config.getLabel();
			if(config.getSortLabel()!=null) {
				labelId = config.getSortLabel();
			}
			
			List<String> l = null;
			if(mapSorted.containsKey(labelId)) {
				l = mapSorted.get(labelId);
			}
			else {
				l = new ArrayList<>();
				mapSorted.put(labelId, l);
				mapSortedKeys.add(labelId);
			}
			l.add(id);
			
		}
		
		Collections.sort(mapSortedKeys);
		List<String> list = new ArrayList<>();
		for (String labelId : mapSortedKeys) {
			List<String> l = mapSorted.get(labelId);
			list.addAll(l);
		}
		return list; // lista contenente gli id
	}
	
	public List<String> convertToLabel(PropertiesSourceConfiguration propertiesSourceConfiguration, List<String> idList){
	
		Map<String, Config> map = this.mapConfig.get(propertiesSourceConfiguration.getId());
		List<String> listLabels = new ArrayList<>();
		for (String id : idList) {
			Config config = map.get(id);
			String labelId = config.getLabel();
			/**if(config.getSortLabel()!=null) {
				labelId = config.getSortLabel();
			}*/
			listLabels.add(labelId);
		}
		return listLabels;
	}
	
	public Config getConfigurazione(PropertiesSourceConfiguration propertiesSourceConfiguration, String name){
		return this.mapConfig.get(propertiesSourceConfiguration.getId()).get(name);
	}
	
	public boolean checkCompatibility(Compatibility compatibility, List<String> tags) {
		if(compatibility == null)
			return true;
		
		boolean isAnd = compatibility.getAnd(); 
		boolean isNot = compatibility.getNot();
		
		// Valore di partenza dell'esito totale e'
		// TRUE se devo controllare l'and dei tag
		// FALSE se devo controllare l'or dei tag
		boolean esito = isAnd;

		for (Tags tag : compatibility.getTagsList()) {
			boolean resCondition = checkTags(tag,tags);

			// aggiorno l'esito in base all'operazione da aggregare AND o OR
			esito = isAnd ? (esito && resCondition) : (esito || resCondition);
		}

		// eventuale NOT della condizione
		return isNot ? !esito : esito;
	}

	private boolean checkTags(Tags tag, List<String> tags) {
		boolean isAnd = tag.getAnd(); 
		boolean isNot = tag.getNot();
		
		// Valore di partenza dell'esito totale e'
		// TRUE se devo controllare l'and dei tag
		// FALSE se devo controllare l'or dei tag
		boolean esito = isAnd;
		
		for (String stringTag : tag.getTagList()) {
			boolean resCondition = tags.contains(stringTag);
			// aggiorno l'esito in base all'operazione da aggregare AND o OR
			esito = isAnd ? (esito && resCondition) : (esito || resCondition);
		}
		
		// eventuale NOT della condizione
		return isNot ? !esito : esito;
	}
	
}