All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.effectiveosgi.rt.config.impl.HierarchicalConfigInstaller Maven / Gradle / Ivy

The newest version!
package com.effectiveosgi.rt.config.impl;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;

import org.apache.felix.fileinstall.ArtifactInstaller;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;

import com.effectiveosgi.rt.config.ConfigFileReader;
import com.effectiveosgi.rt.config.ParsedRecord;
import com.effectiveosgi.rt.config.RecordIdentity;

class HierarchicalConfigInstaller extends ServiceTracker implements ArtifactInstaller {
	
	static final String PID = "com.effectiveosgi.rt.config";

	private static final String PROP_PREFIX = "_" + PID;
	static final String PROP_IDENTITY = PROP_PREFIX + ".identity";
	static final String PROP_FILE_PATH = PROP_PREFIX + ".filePath";
	
	private final class RankedReaderService implements Comparable {
		private final long serviceId;
		private final ConfigFileReader reader;
		private final int rank;
		private final List patterns;
		private RankedReaderService(ConfigFileReader reader, ServiceReference ref) {
			serviceId = (Long) ref.getProperty(Constants.SERVICE_ID);
			this.reader = reader;
			
			Object rankingObj = ref.getProperty(Constants.SERVICE_RANKING);
			rank = (rankingObj instanceof Integer) ? (Integer) rankingObj : 0;
			
			Object patternsObj = ref.getProperty(ConfigFileReader.PROP_FILE_PATTERN);
			final String[] patternStrs;
			if (patternsObj instanceof String) {
				patternStrs = new String[] { (String) patternsObj };
			} else if (patternsObj instanceof String[]) {
				patternStrs = (String[]) patternsObj;
			} else {
				if (log != null) log.log(LogService.LOG_WARNING, String.format("%s service with ID %s from bundle %s_%s has is missing the %s property or it is the wrong type. No files will match.",
						ConfigFileReader.class.getSimpleName(), serviceId, ref.getBundle().getSymbolicName(), ref.getBundle().getVersion(), ConfigFileReader.PROP_FILE_PATTERN));
				patternStrs = new String[0];
			}

			patterns = new ArrayList<>(patternStrs.length);
			for (String patternStr : patternStrs) {
				try {
					patterns.add(Pattern.compile(patternStr));
				} catch (PatternSyntaxException e) {
					if (log != null) log.log(LogService.LOG_WARNING, String.format("Pattern parsing error on %s property of %s service with ID %d from bundle %s_%s: '%s'",
							ConfigFileReader.PROP_FILE_PATTERN, ConfigFileReader.class.getSimpleName(), serviceId, ref.getBundle().getSymbolicName(), ref.getBundle().getVersion(), patternStr), e);
				}
			}
			if (patterns.isEmpty() && log != null) {
				log.log(LogService.LOG_WARNING, String.format("No valid file name patterns on %s service with ID %d from bundle %s_%s. No files will match.",
						ConfigFileReader.class.getSimpleName(), serviceId, ref.getBundle().getSymbolicName(), ref.getBundle().getVersion()));
			}
		}
		@Override
		public int compareTo(RankedReaderService other) {
			int result = this.rank - other.rank;
			if (result == 0) result = (int) (other.serviceId - this.serviceId);
			return result;
		}
		@Override
		public String toString() {
			return "RankedReaderService [serviceId=" + serviceId + ", rank=" + rank + ", patterns=" + patterns + "]";
		}
	}
	
	private final SortedSet rankedReaders = new TreeSet<>();
	private final ReadWriteLock rankedReadersLock = new ReentrantReadWriteLock();

	private final LogService log;
	private final ConfigurationAdmin configAdmin;
	
	HierarchicalConfigInstaller(BundleContext context, ConfigurationAdmin configAdmin, LogService log) {
		super(context, ConfigFileReader.class, null);
		this.configAdmin = configAdmin;
		this.log = log;
	}
	
	@Override
	public ConfigFileReader addingService(ServiceReference reference) {
		ConfigFileReader reader = context.getService(reference);
		RankedReaderService rankedEntry = new RankedReaderService(reader, reference);
		rankedReadersLock.writeLock().lock();
		try {
			rankedReaders.add(rankedEntry);
		} finally {
			rankedReadersLock.writeLock().unlock();
		}
		return reader;
	}
	@Override
	public void removedService(ServiceReference reference, ConfigFileReader reader) {
		long serviceId = (Long) reference.getProperty(Constants.SERVICE_ID);
		rankedReadersLock.writeLock().lock();
		try {
			for (Iterator iter = rankedReaders.iterator(); iter.hasNext(); ) {
				RankedReaderService entry = iter.next();
				if (serviceId == entry.serviceId) {
					iter.remove();
				}
			}
		} finally {
			rankedReadersLock.writeLock().unlock();
		}
	}
	
	private ConfigFileReader findReader(String fileName) {
		rankedReadersLock.readLock().lock();
		try {
			for (RankedReaderService rankedReader : rankedReaders) {
				for (Pattern pattern : rankedReader.patterns) {
					if (pattern.matcher(fileName).matches())
						return rankedReader.reader;
				}
			}
			return null;
		} finally {
			rankedReadersLock.readLock().unlock();
		}
	}

	@Override
	public boolean canHandle(File artifact) {
		return findReader(artifact.getName()) != null;
	}

	@Override
	public void install(File artifact) throws Exception {
		log(LogService.LOG_INFO, "Installing artifact " + artifact.getAbsolutePath(), null);
		loadConfig(artifact);
	}


	@Override
	public void update(File artifact) throws Exception {
		log(LogService.LOG_INFO, "Updating artifact " + artifact.getAbsolutePath(), null);
		loadConfig(artifact);
	}

	@Override
	public void uninstall(File artifact) throws Exception {
		log(LogService.LOG_INFO, "Uninstalling artifact" + artifact.getAbsolutePath(), null);
		deleteConfigs(artifact);
	}
	
	private void loadConfig(File artifact) throws IOException, IllegalArgumentException {
		String fileName = artifact.getName();
		ConfigFileReader reader = findReader(fileName);
		if (reader == null)
			throw new IllegalArgumentException("No reader found for config file name " + fileName);

		// Load the existing configs in ConfigurationAdmin
		Map existingConfigs = loadExistingConfigs(artifact);

		// Read the configs from the file and heal ConfigurationAdmin
		Stream loadedRecords = reader.load(artifact);
		loadedRecords.forEach(record -> {
			Configuration existingConfig = existingConfigs.remove(record.getId());

			try {
				if (existingConfig != null) {
					// Update existing config if it is not different
					if (!matches(existingConfig.getProperties(), record.getProperties())) {
						log(LogService.LOG_DEBUG, String.format("Updating record %s into configuration pid=%s, factoryPid=%s", record.getId(), existingConfig.getPid(), existingConfig.getFactoryPid()), null);
						existingConfig.update(buildConfigDict(artifact, record.getId().getId(), record.getProperties()));
					}
				} else {
					// Create a new configuration
					Configuration config;
					if (record.getId().getFactoryId() != null) {
						config = configAdmin.createFactoryConfiguration(record.getId().getFactoryId(), "?");
					} else {
						config = configAdmin.getConfiguration(record.getId().getId(), "?");
					}
					log(LogService.LOG_DEBUG, String.format("Updating record %s into configuration pid=%s, factoryPid=%s", record.getId(), config.getPid(), config.getFactoryPid()), null);
					config.update(buildConfigDict(artifact, record.getId().getId(), record.getProperties()));
				}
				
			} catch (IOException e) {
				log(LogService.LOG_ERROR, "Failed to modify record: " + record.getId(), e);
			}
		});
		
		// Delete any configurations remaining in the map
		for (Entry entry : existingConfigs.entrySet()) {
			Configuration existingConfig = entry.getValue();
			log(LogService.LOG_DEBUG, String.format("Deleting configuration pid=%s, factoryPid=%s: no matching record %s", existingConfig.getPid(), existingConfig.getFactoryPid(), entry.getKey()), null);
			existingConfig.delete();
		}
	}
	
	private Dictionary buildConfigDict(File artifact, String id, Map map) {
		Dictionary dict = new Hashtable<>(map);
		dict.put(PROP_FILE_PATH, artifact.getAbsolutePath());
		dict.put(PROP_IDENTITY, id);
		return dict;
	}

	private Map loadExistingConfigs(File artifact) throws IOException {
		try {
			final Map map;
			Configuration[] configs = configAdmin.listConfigurations(String.format("(%s=%s)", PROP_FILE_PATH, artifact.getAbsolutePath()));
			if (configs != null) {
				map = new HashMap<>(configs.length);
				for (Configuration config : configs) {
					Dictionary dict = config.getProperties();
					Object identityObj = dict.get(PROP_IDENTITY);

					if (!(identityObj instanceof String)) {
						log(LogService.LOG_WARNING, String.format("Deleting unidentified configuration record, pid=%s factoryPid=%s (missing %s property)", config.getPid(), config.getFactoryPid(), PROP_IDENTITY), null);
						deleteConfigNoException(config);
						continue;
					}
					String identityStr = (String) identityObj;
					RecordIdentity identity = new RecordIdentity(identityStr, config.getFactoryPid());
					map.put(identity, config);
				}
			} else {
				map = new HashMap<>(0);
			}
			return map;
		} catch (InvalidSyntaxException e) {
			throw new IOException("Could not load existing configurations due to invalid filter syntax", e);
		}
	}

	private void deleteConfigs(File artifact) {
		try {
			String filter = String.format("(%s=%s)", PROP_FILE_PATH, artifact.getAbsolutePath());
			Configuration[] configs = configAdmin.listConfigurations(filter);
			if (configs != null) for (Configuration config : configs) {
				try {
					config.delete();
				} catch (Exception e) {
					log(LogService.LOG_ERROR, String.format("Failed to delete config %s for file %s", printConfigId(config), artifact.getAbsoluteFile()), e);
				}
			}
		} catch (IOException | InvalidSyntaxException e) {
			log(LogService.LOG_ERROR, "Error listing configurations for file " + artifact.getAbsolutePath(), null);
		}
	}

	private String printConfigId(Configuration config) {
		return config.getFactoryPid() == null ? String.format("pid=%s", config.getPid()) : String.format("factoryPid=%s, pid=%s", config.getFactoryPid(), config.getPid());
	}

	private void log(int level, String message, Throwable exception) {
		if (log != null) log.log(level, message, exception);
	}

	private void deleteConfigNoException(Configuration config) {
		String pid = config.getPid();
		String factoryPid = config.getFactoryPid();
		try {
			config.delete();
		} catch (IOException e) {
			log(LogService.LOG_WARNING, String.format("Failed to delete configuration pid=%s factoryPid=%s", pid, factoryPid), null);
		}
	}

	private static boolean matches(Dictionary dict, Map record) {
		return filterInfraProperties(dict).equals(record);
	}

	private static Map filterInfraProperties(Dictionary dict) {
		Map result = new HashMap<>(dict.size());
		for (Enumeration e = dict.keys(); e.hasMoreElements();) {
			String key = e.nextElement();
			if (key.startsWith(PROP_PREFIX) || key.equals(Constants.SERVICE_PID) || key.equals(ConfigurationAdmin.SERVICE_FACTORYPID))
				continue;
			result.put(key, dict.get(key));
		}
		return result;
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy