/* * Copyright 2013 Christoph Läubrich * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * * See the License for the specific language governing permissions and * limitations under the License. */ package org.ops4j.pax.exam.cm.internal; import java.io.IOException; import java.util.Dictionary; import java.util.Enumeration; import java.util.Hashtable; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.cm.ConfigurationEvent; import org.osgi.service.cm.ConfigurationListener; import org.osgi.util.tracker.ServiceTrackerCustomizer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Tracks the {@link ConfigurationAdmin} and uses {@link ConfigurationListener} to be notified when * configuration changes */ public class ConfigurationOptionConfigurationListener implements ConfigurationListener, ServiceTrackerCustomizer<ConfigurationAdmin, ConfigurationAdmin> { private static final Logger LOG = LoggerFactory .getLogger(ConfigurationOptionConfigurationListener.class); private final Map<String, Object> properties; private final BundleContext context; private final String pid; private final boolean create; private final boolean override; private final boolean factory; private Configuration factoryConfiguration; public ConfigurationOptionConfigurationListener(String pid, Map<String, Object> properties, BundleContext context, boolean create, boolean override, boolean factory) { this.properties = properties; this.context = context; this.pid = pid; this.create = create; this.override = override; this.factory = factory; } @Override public void configurationEvent(ConfigurationEvent event) { if (!factory && override && event.getPid().equals(pid)) { ServiceReference<ConfigurationAdmin> reference = event.getReference(); ConfigurationAdmin service = context.getService(reference); if (service != null) { try { checkIfConfigurationNeeded(service); } finally { context.ungetService(reference); } } } } /** * Check if an override of config values is needed for the given {@link ConfigurationAdmin} * service, since events can occur asycrounous we synchronized here to handle each event one by * one since this method might trigger other async events also... * * @param service * the {@link ConfigurationAdmin} to check */ private synchronized void checkIfConfigurationNeeded(ConfigurationAdmin service) { try { if (factory) { if (factoryConfiguration == null) { factoryConfiguration = service.createFactoryConfiguration(pid, null); factoryConfiguration.update(new Hashtable<String, Object>(properties)); LOG.info( "Created new factory configuration for factory-pid {}, generated pid is {} with properties: {}", new Object[] { pid, factoryConfiguration.getPid(), properties }); } } else { Configuration configuration = service.getConfiguration(pid, null); Dictionary<String, Object> dictionary = configuration.getProperties(); if (dictionary != null) { boolean update = false; Set<Entry<String, Object>> entrySet = properties.entrySet(); for (Entry<String, Object> entry : entrySet) { Object object = dictionary.get(entry.getKey()); if (object == null) { if (entry.getValue() == null) { // Not changed... continue; } } else { if (object.equals(entry.getValue())) { // Not changed... continue; } } // A change is detected... update = true; dictionary.put(entry.getKey(), entry.getValue()); } if (update) { LOG.info("Update existing configuration for pid {} with properties: {}", pid, dictToString(dictionary)); configuration.update(dictionary); } } else { if (create) { configuration.update(new Hashtable<String, Object>(properties)); LOG.info("Created new configuration for pid {} with properties: {}", pid, properties); } } } } catch (IOException e) { LOG.warn("can't modify configuration for PID {}", pid, e); } } private static Object dictToString(Dictionary<String, Object> dictionary) { StringBuilder sb = new StringBuilder(); Enumeration<String> keys = dictionary.keys(); while (keys.hasMoreElements()) { if (sb.length() == 0) { sb.append("["); } else { sb.append(", "); } String key = keys.nextElement(); sb.append(key); sb.append(": "); sb.append(dictionary.get(key)); } return sb.append("]"); } @Override public ConfigurationAdmin addingService(ServiceReference<ConfigurationAdmin> reference) { ConfigurationAdmin service = (ConfigurationAdmin) context.getService(reference); if (service != null) { checkIfConfigurationNeeded(service); } return service; } @Override public void modifiedService(ServiceReference<ConfigurationAdmin> reference, ConfigurationAdmin service) { // don't care } @Override public void removedService(ServiceReference<ConfigurationAdmin> reference, ConfigurationAdmin service) { if (factoryConfiguration != null) { // We delete it here, just in case the ConfigAdmin is restarted so we are not ending up // with two factory configs try { factoryConfiguration.delete(); } catch (IOException e) { LOG.debug("Deleting factoryConfiguration failed", e); } catch (IllegalStateException e) { // Ignore... it was already deleted! } } context.ungetService(reference); } }