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

org.apache.felix.cm.impl.ConfigurationManager Maven / Gradle / Ivy

There is a newer version: 1.9.26
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.felix.cm.impl;


import java.io.IOException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.Set;

import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.helper.BaseTracker;
import org.apache.felix.cm.impl.helper.ConfigurationMap;
import org.apache.felix.cm.impl.helper.ManagedServiceFactoryTracker;
import org.apache.felix.cm.impl.helper.ManagedServiceTracker;
import org.apache.felix.cm.impl.helper.TargetedPID;
import org.apache.felix.cm.impl.persistence.CachingPersistenceManagerProxy;
import org.apache.felix.cm.impl.persistence.ExtPersistenceManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.osgi.service.cm.ConfigurationPermission;
import org.osgi.service.cm.ConfigurationPlugin;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.cm.SynchronousConfigurationListener;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;


/**
 * The {@code ConfigurationManager} is the central class in this
 * implementation of the Configuration Admin Service Specification. As such it
 * has the following tasks:
 * 
    *
  • It is a BundleListener which gets informed when the * states of bundles change. Mostly this is needed to unbind any bound * configuration in case a bundle is uninstalled. *
  • It is a ServiceListener which gets informed when * ManagedService and ManagedServiceFactory * services are registered and unregistered. This is used to provide * configuration to these services. As a service listener it also listens for * {@link PersistenceManager} instances being registered to support different * configuration persistence layers. *
  • A {@link ConfigurationAdminFactory} instance is registered as the * ConfigurationAdmin service. *
  • Last but not least this instance manages all tasks laid out in the * specification such as maintaining configuration, taking care of configuration * events, etc. *
*/ public class ConfigurationManager implements BundleListener { // random number generator to create configuration PIDs for factory // configurations private static Random numberGenerator; // the BundleContext of the Configuration Admin Service bundle private final BundleContext bundleContext; // the service registration of the configuration admin private volatile ServiceRegistration configurationAdminRegistration; // the ConfigurationEvent listeners private ServiceTracker configurationListenerTracker; // the synchronous ConfigurationEvent listeners private ServiceTracker syncConfigurationListenerTracker; // service tracker for managed services private ManagedServiceTracker managedServiceTracker; // service tracker for managed service factories private ManagedServiceFactoryTracker managedServiceFactoryTracker; // the thread used to schedule tasks required to run asynchronously private UpdateThread updateThread; // the thread used to schedule events to be dispatched asynchronously private UpdateThread eventThread; /** * The persistence manager */ private final ExtPersistenceManager persistenceManager; // the cache of Configuration instances mapped by their PID // have this always set to prevent NPE on bundle shutdown private final HashMap configurations = new HashMap<>(); /** * The map of dynamic configuration bindings. This maps the * PID of the dynamically bound configuration or factory to its bundle * location. *

* On bundle startup this map is loaded from persistence and validated * against the locations of installed bundles: Entries pointing to bundle * locations not currently installed are removed. *

* The map is written to persistence on each change. */ private final DynamicBindings dynamicBindings; // flag indicating whether BundleChange events should be consumed (FELIX-979) private volatile boolean handleBundleEvents; // flag indicating whether the manager is considered alive private volatile boolean isActive; // Coordinator service if available private volatile Object coordinator; public ConfigurationManager(final ExtPersistenceManager persistenceManager, final BundleContext bundleContext) throws IOException { // set up some fields this.bundleContext = bundleContext; this.dynamicBindings = new DynamicBindings( bundleContext, persistenceManager.getDelegatee() ); this.persistenceManager = persistenceManager; } public ServiceReference start() { // configurationlistener support configurationListenerTracker = new ServiceTracker<>( bundleContext, ConfigurationListener.class, null ); configurationListenerTracker.open(); syncConfigurationListenerTracker = new ServiceTracker<>( bundleContext, SynchronousConfigurationListener.class, null ); syncConfigurationListenerTracker.open(); // initialize the asynchonous updater thread ThreadGroup tg = new ThreadGroup( "Configuration Admin Service" ); tg.setDaemon( true ); this.updateThread = new UpdateThread( tg, "CM Configuration Updater" ); this.eventThread = new UpdateThread( tg, "CM Event Dispatcher" ); // register as bundle and service listener handleBundleEvents = true; bundleContext.addBundleListener( this ); // consider alive now (before clients use Configuration Admin // service registered in the next step) isActive = true; // create and register configuration admin - start after PM tracker ... ConfigurationAdminFactory caf = new ConfigurationAdminFactory( this ); Dictionary props = new Hashtable<>(); props.put( Constants.SERVICE_PID, "org.apache.felix.cm.ConfigurationAdmin" ); props.put( Constants.SERVICE_DESCRIPTION, "Configuration Admin Service Specification 1.6 Implementation" ); props.put( Constants.SERVICE_VENDOR, "The Apache Software Foundation" ); props.put( "osgi.command.scope", "cm" ); Set functions = new HashSet<>(); for ( Method method : ConfigurationAdmin.class.getMethods() ) { functions.add(method.getName()); } props.put( "osgi.command.function", functions.toArray(new String[0]) ); configurationAdminRegistration = bundleContext.registerService( ConfigurationAdmin.class, caf, props ); // start handling ManagedService[Factory] services managedServiceTracker = new ManagedServiceTracker(this); managedServiceFactoryTracker = new ManagedServiceFactoryTracker(this); // start processing the event queues only after registering the service // see FELIX-2813 for details this.updateThread.start(); this.eventThread.start(); return configurationAdminRegistration.getReference(); } public void stop( ) { // stop handling bundle events immediately handleBundleEvents = false; // stop handling ManagedService[Factory] services managedServiceFactoryTracker.close(); managedServiceTracker.close(); // stop queue processing before unregistering the service // see FELIX-2813 for details if ( updateThread != null ) { updateThread.terminate(); } if ( eventThread != null ) { eventThread.terminate(); } // immediately unregister the Configuration Admin before cleaning up // clearing the field before actually unregistering the service // prevents IllegalStateException in getServiceReference() if // the field is not null but the service already unregistered final ServiceRegistration caReg = configurationAdminRegistration; configurationAdminRegistration = null; if ( caReg != null ) { caReg.unregister(); } // consider inactive after unregistering such that during // unregistration the manager is still alive and can react isActive = false; // stop listening for events bundleContext.removeBundleListener( this ); if ( configurationListenerTracker != null ) { configurationListenerTracker.close(); } if ( syncConfigurationListenerTracker != null ) { syncConfigurationListenerTracker.close(); } // just ensure the configuration cache is empty synchronized ( configurations ) { configurations.clear(); } } /** * Returns true if this manager is considered active. */ boolean isActive() { return isActive; } public BundleContext getBundleContext() { return bundleContext; } // ---------- Configuration caching support -------------------------------- ConfigurationImpl getCachedConfiguration( String pid ) { synchronized ( configurations ) { return configurations.get( pid ); } } ConfigurationImpl[] getCachedConfigurations() { synchronized ( configurations ) { return configurations.values().toArray( new ConfigurationImpl[configurations.size()] ); } } ConfigurationImpl cacheConfiguration( ConfigurationImpl configuration ) { synchronized ( configurations ) { final String pid = configuration.getPidString(); final Object existing = configurations.get( pid ); if ( existing != null ) { return ( ConfigurationImpl ) existing; } configurations.put( pid, configuration ); return configuration; } } void removeConfiguration( ConfigurationImpl configuration ) { synchronized ( configurations ) { configurations.remove( configuration.getPidString() ); } } // ---------- ConfigurationAdminImpl support void setDynamicBundleLocation( final String pid, final String location ) { if ( dynamicBindings != null ) { try { dynamicBindings.putLocation( pid, location ); } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Failed storing dynamic configuration binding for {0} to {1}", new Object[] { pid, location, ioe } ); } } } String getDynamicBundleLocation( final String pid ) { if ( dynamicBindings != null ) { return dynamicBindings.getLocation( pid ); } return null; } ConfigurationImpl createFactoryConfiguration( String factoryPid, String location ) throws IOException { return cacheConfiguration( internalCreateConfiguration( createPid( factoryPid ), factoryPid, location ) ); } ConfigurationImpl createFactoryConfiguration(String pid, String factoryPid, String location ) throws IOException { return cacheConfiguration( internalCreateConfiguration( pid, factoryPid, location ) ); } /** * Returns a targeted configuration for the given service PID and * the reference target service. *

* A configuration returned has already been checked for visibility * by the bundle registering the referenced service. Additionally, * the configuration is also dynamically bound if needed. * * @param rawPid The raw service PID to get targeted configuration for. * @param target The target ServiceReference to get * configuration for. * @return The best matching targeted configuration or null * if there is no configuration at all. * @throwss IOException if an error occurrs reading configurations * from persistence. */ ConfigurationImpl getTargetedConfiguration( final String rawPid, final ServiceReference target ) throws IOException { final Bundle serviceBundle = target.getBundle(); if ( serviceBundle != null ) { // list of targeted PIDs to check final StringBuilder targetedPid = new StringBuilder( rawPid ); int i = 3; String[] names = new String[4]; names[i--] = targetedPid.toString(); targetedPid.append( '|' ).append( serviceBundle.getSymbolicName() ); names[i--] = targetedPid.toString(); targetedPid.append( '|' ).append( serviceBundle.getVersion().toString() ); names[i--] = targetedPid.toString(); targetedPid.append( '|' ).append( Activator.getLocation(serviceBundle) ); names[i--] = targetedPid.toString(); for ( String candidate : names ) { ConfigurationImpl config = getConfiguration( candidate ); if ( config != null && !config.isDeleted() ) { // check visibility to use and dynamically bind if ( canReceive( serviceBundle, config.getBundleLocation() ) ) { config.tryBindLocation( Activator.getLocation(serviceBundle) ); return config; } // CM 1.4 / 104.13.2.2 / 104.5.3 // act as if there is no configuration Log.logger.log( LogService.LOG_DEBUG, "Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}; calling with null", new Object[] { config.getPid(), target , config.getBundleLocation() } ); } } } else { Log.logger.log( LogService.LOG_INFO, "Service for PID {0} seems to already have been unregistered, not updating with configuration", new Object[] { rawPid } ); } // service already unregistered, nothing to do really return null; } /** * Returns the {@link ConfigurationImpl} with the given PID if * available in the internal cache or from any persistence manager. * Otherwise null is returned. * * @param pid The PID for which to return the configuration * @return The configuration or null if non exists * @throws IOException If an error occurs reading from a persistence * manager. */ ConfigurationImpl getConfiguration( String pid ) throws IOException { ConfigurationImpl config = getCachedConfiguration( pid ); if ( config != null ) { Log.logger.log( LogService.LOG_DEBUG, "Found cached configuration {0} bound to {1}", new Object[] { pid, config.getBundleLocation() } ); config.ensureFactoryConfigPersisted(); return config; } if ( this.persistenceManager.exists( pid ) ) { final Dictionary props = this.persistenceManager.load( pid ); config = new ConfigurationImpl( this, this.persistenceManager, props ); Log.logger.log( LogService.LOG_DEBUG, "Found existing configuration {0} bound to {1}", new Object[] { pid, config.getBundleLocation() } ); return cacheConfiguration( config ); } // neither the cache nor the persistence manager has configuration return null; } /** * Creates a regular (non-factory) configuration for the given PID * setting the bundle location accordingly. *

* This method assumes the configuration to not exist yet and will * create it without further checking. * * @param pid The PID of the new configuration * @param bundleLocation The location to set on the new configuration. * This may be null to not bind the configuration * yet. * @return The new configuration persisted in the first persistence * manager. * @throws IOException If an error occurrs writing the configuration * to the persistence. */ ConfigurationImpl createConfiguration( String pid, String bundleLocation ) throws IOException { // check for existing (cached or persistent) configuration ConfigurationImpl config = getConfiguration( pid ); if ( config != null ) { return config; } // else create new configuration also setting the bundle location // and cache the new configuration config = internalCreateConfiguration( pid, null, bundleLocation ); return cacheConfiguration( config ); } ConfigurationImpl[] listConfigurations( ConfigurationAdminImpl configurationAdmin, String filterString ) throws IOException, InvalidSyntaxException { SimpleFilter filter = null; if ( filterString != null ) { filter = SimpleFilter.parse( filterString ); } Log.logger.log( LogService.LOG_DEBUG, "Listing configurations matching {0}", new Object[] { filterString } ); List configList = new ArrayList<>(); Collection configs = this.persistenceManager.getDictionaries(filter ); for(final Dictionary config : configs) { // ignore non-Configuration dictionaries final String pid = ( String ) config.get( Constants.SERVICE_PID ); if ( pid == null ) { continue; } // CM 1.4 / 104.13.2.3 Permission required if ( !configurationAdmin.hasPermission( this, ( String ) config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) ) ) { Log.logger.log( LogService.LOG_DEBUG, "Omitting configuration {0}: No permission for bundle {1} on configuration bound to {2}", new Object[] { pid, Activator.getLocation(configurationAdmin.getBundle()), config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) } ); continue; } // ensure the service.pid and returned a cached config if available ConfigurationImpl cfg = null; if ( this.persistenceManager instanceof CachingPersistenceManagerProxy) { cfg = getCachedConfiguration( pid ); if (cfg == null) { cfg = new ConfigurationImpl(this, this.persistenceManager, config); // add the to configurations cache if it wasn't in the cache cacheConfiguration(cfg); } } else { cfg = new ConfigurationImpl( this, this.persistenceManager, config ); } // FELIX-611: Ignore configuration objects without props if ( !cfg.isNew() ) { Log.logger.log( LogService.LOG_DEBUG, "Adding configuration {0}", new Object[] { pid } ); configList.add( cfg ); } else { Log.logger.log( LogService.LOG_DEBUG, "Omitting configuration {0}: Is new", new Object[] { pid } ); } } if ( configList.size() == 0 ) { return null; } return configList.toArray( new ConfigurationImpl[configList .size()] ); } void deleted( ConfigurationImpl config ) { // remove the configuration from the cache removeConfiguration( config ); fireConfigurationEvent( ConfigurationEvent.CM_DELETED, config.getPidString(), config.getFactoryPidString() ); final Runnable task = new DeleteConfiguration( config ); if ( this.coordinator == null || !CoordinatorUtil.addToCoordination(this.coordinator, updateThread, task) ) { updateThread.schedule( task ); } Log.logger.log( LogService.LOG_DEBUG, "DeleteConfiguration({0}) scheduled", new Object[] { config.getPid() } ); } void updated( ConfigurationImpl config, boolean fireEvent ) { if ( fireEvent ) { fireConfigurationEvent( ConfigurationEvent.CM_UPDATED, config.getPidString(), config.getFactoryPidString() ); } final Runnable task = new UpdateConfiguration( config ); if ( this.coordinator == null || !CoordinatorUtil.addToCoordination(this.coordinator, updateThread, task) ) { updateThread.schedule( task ); } Log.logger.log( LogService.LOG_DEBUG, "UpdateConfiguration({0}) scheduled", new Object[] { config.getPid() } ); } void locationChanged( ConfigurationImpl config, String oldLocation ) { fireConfigurationEvent( ConfigurationEvent.CM_LOCATION_CHANGED, config.getPidString(), config.getFactoryPidString() ); if ( oldLocation != null && !config.isNew() ) { final Runnable task = new LocationChanged( config, oldLocation ); if ( this.coordinator == null || !CoordinatorUtil.addToCoordination(this.coordinator, updateThread, task) ) { updateThread.schedule( task ); } Log.logger.log( LogService.LOG_DEBUG, "LocationChanged({0}, {1}=>{2}) scheduled", new Object[] { config.getPid(), oldLocation, config.getBundleLocation() } ); } else { Log.logger.log( LogService.LOG_DEBUG, "LocationChanged not scheduled for {0} (old location is null or configuration is new)", new Object[] { config.getPid() } ); } } void fireConfigurationEvent( int type, String pid, String factoryPid ) { // prevent event senders FireConfigurationEvent asyncSender = new FireConfigurationEvent( this.configurationListenerTracker, type, pid, factoryPid ); FireConfigurationEvent syncSender = new FireConfigurationEvent( this.syncConfigurationListenerTracker, type, pid, factoryPid ); // send synchronous events if ( syncSender.hasConfigurationEventListeners() ) { syncSender.run(); } else { Log.logger.log( LogService.LOG_DEBUG, "No SynchronousConfigurationListeners to send {0} event to.", new Object[] { syncSender.getTypeName() } ); } // schedule asynchronous events if ( asyncSender.hasConfigurationEventListeners() ) { if ( this.coordinator == null || !CoordinatorUtil.addToCoordination(this.coordinator, eventThread, asyncSender) ) { eventThread.schedule( asyncSender ); } } else { Log.logger.log( LogService.LOG_DEBUG, "No ConfigurationListeners to send {0} event to.", new Object[] { asyncSender.getTypeName() } ); } } // ---------- BundleListener ----------------------------------------------- @Override public void bundleChanged( BundleEvent event ) { if ( event.getType() == BundleEvent.UNINSTALLED && handleBundleEvents ) { final String location = Activator.getLocation(event.getBundle()); // we only reset dynamic bindings, which are only present in // cached configurations, hence only consider cached configs here final ConfigurationImpl[] configs = getCachedConfigurations(); for ( int i = 0; i < configs.length; i++ ) { final ConfigurationImpl cfg = configs[i]; if ( location.equals( cfg.getDynamicBundleLocation() ) ) { cfg.setDynamicBundleLocation( null, true ); } } } } // ---------- internal ----------------------------------------------------- private ServiceReference getServiceReference() { ServiceRegistration reg = configurationAdminRegistration; if (reg != null) { return reg.getReference(); } // probably called for firing an event during service registration // since we didn't get the service registration yet we use the // service registry to get our service reference BundleContext context = bundleContext; if ( context != null ) { try { Collection> refs = context.getServiceReferences( ConfigurationAdmin.class, null ); if ( refs != null && !refs.isEmpty()) { for(final ServiceReference ref : refs) { if ( ref.getBundle().getBundleId() == context.getBundle().getBundleId() ) { return ref; } } } } catch ( InvalidSyntaxException e ) { // unexpected since there is no filter } } // service references return null; } /** * Configures the ManagedService and returns the service.pid * service property as a String[], which may be null if * the ManagedService does not have such a property. */ /** * Configures the ManagedServiceFactory and returns the service.pid * service property as a String[], which may be null if * the ManagedServiceFactory does not have such a property. */ /** * Schedules the configuration of the referenced service with * configuration for the given PID. * * @param pid The list of service PID of the configurations to be * provided to the referenced service. * @param sr The ServiceReference to the service * to be configured. * @param factory true If the service is considered to * be a ManagedServiceFactory. Otherwise the service * is considered to be a ManagedService. */ public void configure( String[] pid, ServiceReference sr, final boolean factory, final ConfigurationMap configs ) { if ( Log.logger.isLogEnabled( LogService.LOG_DEBUG ) ) { Log.logger.log( LogService.LOG_DEBUG, "configure(ManagedService {0})", new Object[] { sr } ); } Runnable r; if ( factory ) { r = new ManagedServiceFactoryUpdate( pid, sr, configs ); } else { r = new ManagedServiceUpdate( pid, sr, configs ); } if ( this.coordinator == null || !CoordinatorUtil.addToCoordination(this.coordinator, updateThread, r) ) { updateThread.schedule( r ); } Log.logger.log( LogService.LOG_DEBUG, "[{0}] scheduled", new Object[] { r } ); } /** * Factory method to create a new configuration object. The configuration * object returned is not stored in configuration cache and only persisted * if the factoryPid parameter is null. * * @param pid * The PID of the new configuration object. Must not be * null. * @param factoryPid * The factory PID of the new configuration. Not * null if the new configuration object belongs to a * factory. The configuration object will not be persisted if * this parameter is not null. * @param bundleLocation * The bundle location of the bundle to which the configuration * belongs or null if the configuration is not bound * yet. * @return The new configuration object * @throws IOException * May be thrown if an error occurrs persisting the new * configuration object. */ private ConfigurationImpl internalCreateConfiguration( String pid, String factoryPid, String bundleLocation ) throws IOException { Log.logger.log( LogService.LOG_DEBUG, "createConfiguration({0}, {1}, {2})", new Object[] { pid, factoryPid, bundleLocation } ); return new ConfigurationImpl( this, this.persistenceManager, pid, factoryPid, bundleLocation ); } /** * Returns a list of {@link Factory} instances according to the * Configuration Admin 1.5 specification for targeted PIDs (Section * 104.3.2) * * @param rawFactoryPid The raw factory PID without any targettng. * @param target The ServiceReference of the service to * be supplied with targeted configuration. * @return A list of {@link Factory} instances as listed above. This * list will always at least include an instance for the * rawFactoryPid. Other instances are only included * if existing. * @throws IOException If an error occurs reading any of the * {@link Factory} instances from persistence */ List getTargetedFactories( final String rawFactoryPid, final ServiceReference target ) throws IOException { List factories = new LinkedList<>(); final Bundle serviceBundle = target.getBundle(); if ( serviceBundle != null ) { final StringBuilder targetedPid = new StringBuilder( rawFactoryPid ); factories.add( targetedPid.toString() ); targetedPid.append( '|' ).append( serviceBundle.getSymbolicName() ); factories.add( 0, targetedPid.toString() ); targetedPid.append( '|' ).append( serviceBundle.getVersion().toString() ); factories.add( 0, targetedPid.toString() ); targetedPid.append( '|' ).append( Activator.getLocation(serviceBundle) ); factories.add( 0, targetedPid.toString() ); } return factories; } /** * Calls the registered configuration plugins on the given configuration * properties from the given configuration object. *

* The plugins to be called are selected as ConfigurationPlugin * services registered with a cm.target property set to * * or the factory PID of the configuration (for factory * configurations) or the PID of the configuration (for non-factory * configurations). * * @param props The configuration properties run through the registered * ConfigurationPlugin services. This must not be * null. * @param sr The service reference of the managed service (factory) which * is to be updated with configuration * @param configPid The PID of the configuration object whose properties * are to be augmented * @param factoryPid the factory PID of the configuration object whose * properties are to be augmented. This is non-null * only for a factory configuration. */ public void callPlugins( final Dictionary props, final ServiceReference sr, final String configPid, final String factoryPid ) { ServiceReference[] plugins = null; try { final String targetPid = (factoryPid == null) ? configPid : factoryPid; String filter = "(|(!(cm.target=*))(cm.target=" + targetPid + "))"; plugins = bundleContext.getServiceReferences( ConfigurationPlugin.class.getName(), filter ); } catch ( InvalidSyntaxException ise ) { // no filter, no exception ... } // abort early if there are no plugins if ( plugins == null || plugins.length == 0 ) { return; } // sort the plugins by their service.cmRanking if ( plugins.length > 1 ) { Arrays.sort( plugins, RankingComparator.CM_RANKING ); } // call the plugins in order for ( int i = 0; i < plugins.length; i++ ) { ServiceReference pluginRef = plugins[i]; ConfigurationPlugin plugin = ( ConfigurationPlugin ) bundleContext.getService( pluginRef ); if ( plugin != null ) { // if cmRanking is below 0 or above 1000, ignore modifications from the plugin boolean ignore = false; Object rankObj = pluginRef.getProperty( ConfigurationPlugin.CM_RANKING ); if ( rankObj instanceof Integer ) { final int ranking = ( ( Integer ) rankObj ).intValue(); ignore = (ranking < 0 ) || (ranking > 1000); } try { plugin.modifyConfiguration( sr, ignore ? CaseInsensitiveDictionary.unmodifiable(props) : props ); } catch ( Throwable t ) { Log.logger.log( LogService.LOG_ERROR, "Unexpected problem calling configuration plugin {0}", new Object[] { pluginRef , t } ); } finally { // ensure ungetting the plugin bundleContext.ungetService( pluginRef ); } ConfigurationImpl.setAutoProperties( props, configPid, factoryPid ); } } } /** * Creates a PID for the given factoryPid * * @param factoryPid * @return */ private static String createPid( String factoryPid ) { Random ng = numberGenerator; if ( ng == null ) { // FELIX-2771 Secure Random not available on Mika try { ng = new SecureRandom(); } catch ( Throwable t ) { // fall back to Random ng = new Random(); } } byte[] randomBytes = new byte[16]; ng.nextBytes( randomBytes ); randomBytes[6] &= 0x0f; /* clear version */ randomBytes[6] |= 0x40; /* set to version 4 */ randomBytes[8] &= 0x3f; /* clear variant */ randomBytes[8] |= 0x80; /* set to IETF variant */ StringBuilder buf = new StringBuilder( factoryPid.length() + 1 + 36 ); // prefix the new pid with the factory pid buf.append( factoryPid ).append( "." ); // serialize the UUID into the buffer for ( int i = 0; i < randomBytes.length; i++ ) { if ( i == 4 || i == 6 || i == 8 || i == 10 ) { buf.append( '-' ); } int val = randomBytes[i] & 0xff; buf.append( Integer.toHexString( val >> 4 ) ); buf.append( Integer.toHexString( val & 0xf ) ); } return buf.toString(); } /** * Checks whether the bundle is allowed to receive the configuration * with the given location binding. *

* This method implements the logic defined CM 1.4 / 104.4.1: *

    *
  • If the location is null (the configuration is not * bound yet), assume the bundle is allowed
  • *
  • If the location is a single location (no leading "?"), require * the bundle's location to match
  • *
  • If the location is a multi-location (leading "?"), assume the * bundle is allowed if there is no security manager. If there is a * security manager, check whether the bundle has "target" permission * on this location.
  • *
*/ boolean canReceive( final Bundle bundle, final String location ) { if ( location == null ) { Log.logger.log( LogService.LOG_DEBUG, "canReceive=true; bundle={0}; configuration=(unbound)", new Object[] { Activator.getLocation(bundle) } ); return true; } else if ( location.startsWith( "?" ) ) { // multi-location if ( System.getSecurityManager() != null ) { final boolean hasPermission = bundle.hasPermission( new ConfigurationPermission( location, ConfigurationPermission.TARGET ) ); Log.logger.log( LogService.LOG_DEBUG, "canReceive={0}: bundle={1}; configuration={2} (SecurityManager check)", new Object[] { new Boolean( hasPermission ), Activator.getLocation(bundle), location } ); return hasPermission; } Log.logger.log( LogService.LOG_DEBUG, "canReceive=true; bundle={0}; configuration={1} (no SecurityManager)", new Object[] { Activator.getLocation(bundle), location } ); return true; } else { // single location, must match final boolean hasPermission = location.equals( Activator.getLocation(bundle) ); Log.logger.log( LogService.LOG_DEBUG, "canReceive={0}: bundle={1}; configuration={2}", new Object[] { new Boolean( hasPermission ), Activator.getLocation(bundle), location } ); return hasPermission; } } // ---------- inner classes /** * The ManagedServiceUpdate updates a freshly registered * ManagedService with a specific configuration. If a * ManagedService is registered with multiple PIDs an instance of this * class is used for each registered PID. */ private class ManagedServiceUpdate implements Runnable { private final String[] pids; private final ServiceReference sr; private final ConfigurationMap configs; ManagedServiceUpdate( String[] pids, ServiceReference sr, ConfigurationMap configs ) { this.pids = pids; this.sr = sr; this.configs = configs; } @Override public void run() { for ( String pid : this.pids ) { try { final ConfigurationImpl config = getTargetedConfiguration( pid, this.sr ); provide( pid, config ); } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[] { pid, ioe } ); } catch ( Exception e ) { Log.logger.log( LogService.LOG_ERROR, "Unexpected problem providing configuration {0} to service {1}", new Object[] { pid, this.sr, e } ); } } } private void provide(final String servicePid, final ConfigurationImpl config) { // check configuration final TargetedPID configPid; final Dictionary properties; final long revision; if ( config != null ) { synchronized ( config ) { configPid = config.getPid(); properties = config.getProperties( true ); revision = config.getRevision(); } } else { // 104.5.3 ManagedService.updated must be called with null // if no configuration is available configPid = new TargetedPID( servicePid ); properties = null; revision = -1; } Log.logger.log( LogService.LOG_DEBUG, "Updating service {0} with configuration {1}@{2}", new Object[] { servicePid, configPid, new Long( revision ) } ); managedServiceTracker.provideConfiguration( sr, configPid, null, properties, revision, this.configs ); } @Override public String toString() { return "ManagedService Update: pid=" + Arrays.asList( pids ); } } /** * The ManagedServiceFactoryUpdate updates a freshly * registered ManagedServiceFactory with a specific * configuration. If a ManagedServiceFactory is registered with * multiple PIDs an instance of this class is used for each registered * PID. */ private class ManagedServiceFactoryUpdate implements Runnable { private final String[] factoryPids; private final ServiceReference sr; private final ConfigurationMap configs; ManagedServiceFactoryUpdate( String[] factoryPids, ServiceReference sr, final ConfigurationMap configs ) { this.factoryPids = factoryPids; this.sr = sr; this.configs = configs; } @Override public void run() { for ( String factoryPid : this.factoryPids ) { try { final List targetedFactoryPids = getTargetedFactories( factoryPid, sr ); final Set pids = persistenceManager.getFactoryConfigurationPids(targetedFactoryPids); for ( final String pid : pids ) { ConfigurationImpl cfg; try { cfg = getConfiguration( pid ); } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[] { pid, ioe } ); continue; } // sanity check on the configuration if ( cfg == null ) { Log.logger.log( LogService.LOG_ERROR, "Configuration {0} referred to by factory {1} does not exist", new Object[] { pid, factoryPid } ); continue; } else if ( cfg.isNew() ) { // Configuration has just been created but not yet updated // we currently just ignore it and have the update mechanism // provide the configuration to the ManagedServiceFactory // As of FELIX-612 (not storing new factory configurations) // this should not happen. We keep this for added stability // but raise the logging level to error. Log.logger.log( LogService.LOG_ERROR, "Ignoring new configuration pid={0}", new Object[] { pid } ); continue; } provide( factoryPid, cfg ); } } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Cannot get factory mapping for factory PID {0}", new Object[] { factoryPid, ioe } ); } } } private void provide(final String factoryPid, final ConfigurationImpl config) { final Dictionary rawProperties; final long revision; synchronized ( config ) { rawProperties = config.getProperties( true ); revision = config.getRevision(); } Log.logger.log( LogService.LOG_DEBUG, "Updating service {0} with configuration {1}/{2}@{3}", new Object[] { factoryPid, config.getFactoryPid(), config.getPid(), new Long( revision ) } ); // CM 1.4 / 104.13.2.1 final Bundle serviceBundle = this.sr.getBundle(); if ( serviceBundle == null ) { Log.logger.log( LogService.LOG_INFO, "ManagedServiceFactory for factory PID {0} seems to already have been unregistered, not updating with factory", new Object[] { factoryPid } ); return; } if ( !canReceive( serviceBundle, config.getBundleLocation() ) ) { Log.logger.log( LogService.LOG_ERROR, "Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}", new Object[] { config.getPid(), sr , config.getBundleLocation() } ); // no service, really, bail out return; } // 104.4.2 Dynamic Binding config.tryBindLocation( Activator.getLocation(serviceBundle) ); // update the service with the configuration (if non-null) if ( rawProperties != null ) { Log.logger.log( LogService.LOG_DEBUG, "{0}: Updating configuration pid={1}", new Object[] { sr, config.getPid() } ); managedServiceFactoryTracker.provideConfiguration( sr, config.getPid(), config.getFactoryPid(), rawProperties, revision, this.configs ); } } @Override public String toString() { return "ManagedServiceFactory Update: factoryPid=" + Arrays.asList( this.factoryPids ); } } private abstract class ConfigurationProvider implements Runnable { protected final ConfigurationImpl config; protected final long revision; protected final Dictionary properties; private BaseTracker helper; protected ConfigurationProvider( final ConfigurationImpl config ) { synchronized ( config ) { this.config = config; this.revision = config.getRevision(); this.properties = config.getProperties( true ); } } protected TargetedPID getTargetedServicePid() { final TargetedPID factoryPid = this.config.getFactoryPid(); if ( factoryPid != null ) { return factoryPid; } return this.config.getPid(); } protected BaseTracker getHelper() { if ( this.helper == null ) { this.helper = ( BaseTracker ) ( ( this.config.getFactoryPid() == null ) ? ConfigurationManager.this.managedServiceTracker : ConfigurationManager.this.managedServiceFactoryTracker ); } return this.helper; } protected boolean provideReplacement( ServiceReference sr ) { if ( this.config.getFactoryPid() == null ) { try { final String configPidString = this.getHelper().getServicePid( sr, this.config.getPid() ); if (configPidString == null) { return false; // The managed service is not registered anymore in the OSGi service registry. } final ConfigurationImpl rc = getTargetedConfiguration( configPidString, sr ); if ( rc != null ) { final TargetedPID configPid; final Dictionary properties; final long revision; synchronized ( rc ) { configPid = rc.getPid(); properties = rc.getProperties( true ); revision = rc.getRevision(); } this.getHelper().provideConfiguration( sr, configPid, null, properties, -revision, null ); return true; } } catch ( IOException ioe ) { Log.logger.log( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[] { this.config.getPid(), ioe } ); } catch ( Exception e ) { Log.logger.log( LogService.LOG_ERROR, "Unexpected problem providing configuration {0} to service {1}", new Object[] { this.config.getPid(), sr, e } ); } } // factory or no replacement available return false; } } /** * The UpdateConfiguration is used to update * ManagedService[Factory] services with the configuration * they are subscribed to. This may cause the configuration to be * supplied to multiple services. */ private class UpdateConfiguration extends ConfigurationProvider { UpdateConfiguration( final ConfigurationImpl config ) { super( config ); } @Override public void run() { Log.logger.log( LogService.LOG_DEBUG, "Updating configuration {0} to revision #{1}", new Object[] { config.getPid(), new Long( revision ) } ); final List> srList = this.getHelper().getServices( getTargetedServicePid() ); if ( !srList.isEmpty() ) { // optionally bind dynamically to the first service Bundle bundle = srList.get(0).getBundle(); if (bundle == null) { Log.logger.log( LogService.LOG_DEBUG, "Service {0} seems to be unregistered concurrently (not providing configuration)", new Object[] { srList.get(0) } ); return; } config.tryBindLocation( Activator.getLocation(bundle) ); final String configBundleLocation = config.getBundleLocation(); // provide configuration to all services from the // correct bundle for (ServiceReference ref : srList) { final Bundle refBundle = ref.getBundle(); if ( refBundle == null ) { Log.logger.log( LogService.LOG_DEBUG, "Service {0} seems to be unregistered concurrently (not providing configuration)", new Object[] { ref } ); } else if ( canReceive( refBundle, configBundleLocation ) ) { this.getHelper().provideConfiguration( ref, this.config.getPid(), this.config.getFactoryPid(), this.properties, this.revision, null ); } else { // CM 1.4 / 104.13.2.2 Log.logger.log( LogService.LOG_ERROR, "Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}", new Object[] { config.getPid(), ref, configBundleLocation } ); } } } else if ( Log.logger.isLogEnabled( LogService.LOG_DEBUG ) ) { Log.logger.log( LogService.LOG_DEBUG, "No ManagedService[Factory] registered for updates to configuration {0}", new Object[] { config.getPid() } ); } } @Override public String toString() { return "Update: pid=" + config.getPid(); } } /** * The DeleteConfiguration class is used to inform * ManagedService[Factory] services of a configuration * being deleted. */ private class DeleteConfiguration extends ConfigurationProvider { private final String configLocation; DeleteConfiguration( ConfigurationImpl config ) { /* * NOTE: We keep the configuration because it might be cleared just * after calling this method. The pid and factoryPid fields are * final and cannot be reset. */ super(config); this.configLocation = config.getBundleLocation(); } @Override public void run() { List> srList = this.getHelper().getServices( getTargetedServicePid() ); if ( !srList.isEmpty() ) { for (ServiceReference sr : srList) { final Bundle srBundle = sr.getBundle(); if ( srBundle == null ) { Log.logger.log( LogService.LOG_DEBUG, "Service {0} seems to be unregistered concurrently (not removing configuration)", new Object[] { sr } ); } else if ( canReceive( srBundle, configLocation ) ) { // revoke configuration unless a replacement // configuration can be provided if ( !this.provideReplacement( sr ) ) { this.getHelper().removeConfiguration( sr, this.config.getPid(), this.config.getFactoryPid() ); } } else { // CM 1.4 / 104.13.2.2 Log.logger.log( LogService.LOG_ERROR, "Cannot remove configuration {0} for {1}: No visibility to configuration bound to {2}", new Object[] { config.getPid(), sr, configLocation } ); } } } } @Override public String toString() { return "Delete: pid=" + config.getPid(); } } private class LocationChanged extends ConfigurationProvider { private final String oldLocation; LocationChanged( ConfigurationImpl config, String oldLocation ) { super( config ); this.oldLocation = oldLocation; } @Override public void run() { List> srList = this.getHelper().getServices( getTargetedServicePid() ); if ( !srList.isEmpty() ) { for (final ServiceReference sr : srList) { final Bundle srBundle = sr.getBundle(); if ( srBundle == null ) { Log.logger.log( LogService.LOG_DEBUG, "Service {0} seems to be unregistered concurrently (not processing)", new Object[] { sr } ); continue; } final boolean wasVisible = canReceive( srBundle, oldLocation ); final boolean isVisible = canReceive( srBundle, config.getBundleLocation() ); // make sure the config is dynamically bound to the first // service if the config has been unbound causing this update if ( isVisible ) { config.tryBindLocation( Activator.getLocation(srBundle) ); } if ( wasVisible && !isVisible ) { // revoke configuration unless a replacement // configuration can be provided if ( !this.provideReplacement( sr ) ) { this.getHelper().removeConfiguration( sr, this.config.getPid(), this.config.getFactoryPid() ); Log.logger.log( LogService.LOG_DEBUG, "Configuration {0} revoked from {1} (no more visibility)", new Object[] { config.getPid(), sr } ); } } else if ( !wasVisible && isVisible ) { // call updated method this.getHelper().provideConfiguration( sr, this.config.getPid(), this.config.getFactoryPid(), this.properties, this.revision, null ); Log.logger.log( LogService.LOG_DEBUG, "Configuration {0} provided to {1} (new visibility)", new Object[] { config.getPid(), sr } ); } else { // same visibility as before Log.logger.log( LogService.LOG_DEBUG, "Unmodified visibility to configuration {0} for {1}", new Object[] { config.getPid(), sr } ); } } } } @Override public String toString() { return "Location Changed (pid=" + config.getPid() + "): " + oldLocation + " ==> " + config.getBundleLocation(); } } private class FireConfigurationEvent implements Runnable { private final int type; private final String pid; private final String factoryPid; private final ServiceReference[] listenerReferences; private final ConfigurationListener[] listeners; private final Bundle[] listenerProvider; private ConfigurationEvent event; private FireConfigurationEvent( final ServiceTracker listenerTracker, final int type, final String pid, final String factoryPid) { this.type = type; this.pid = pid; this.factoryPid = factoryPid; final ServiceReference[] srs = listenerTracker.getServiceReferences(); if ( srs == null || srs.length == 0 ) { this.listenerReferences = null; this.listeners = null; this.listenerProvider = null; } else { this.listenerReferences = srs; this.listeners = new ConfigurationListener[srs.length]; this.listenerProvider = new Bundle[srs.length]; for ( int i = 0; i < srs.length; i++ ) { this.listeners[i] = ( ConfigurationListener ) listenerTracker.getService( srs[i] ); this.listenerProvider[i] = srs[i].getBundle(); } } } boolean hasConfigurationEventListeners() { return this.listenerReferences != null; } String getTypeName() { switch ( type ) { case ConfigurationEvent.CM_DELETED: return "CM_DELETED"; case ConfigurationEvent.CM_UPDATED: return "CM_UPDATED"; case ConfigurationEvent.CM_LOCATION_CHANGED: return "CM_LOCATION_CHANGED"; default: return ""; } } @Override public void run() { for ( int i = 0; i < listeners.length; i++ ) { sendEvent( i ); } } @Override public String toString() { return "Fire ConfigurationEvent: pid=" + pid; } private ConfigurationEvent getConfigurationEvent(ServiceReference serviceReference) { if ( event == null ) { this.event = new ConfigurationEvent( serviceReference, type, factoryPid, pid ); } return event; } private void sendEvent( final int serviceIndex ) { if ( (listenerProvider[serviceIndex].getState() & (Bundle.ACTIVE | Bundle.STARTING)) > 0 && this.listeners[serviceIndex] != null ) { Log.logger.log( LogService.LOG_DEBUG, "Sending {0} event for {1} to {2}", new Object[] { getTypeName(), pid, listenerReferences[serviceIndex]} ); final ServiceReference serviceReference = getServiceReference(); if (serviceReference == null) { Log.logger.log( LogService.LOG_WARNING, "No ConfigurationAdmin for delivering configuration event to {0}", new Object[] { listenerReferences[serviceIndex] } ); return; } try { if ( System.getSecurityManager() != null ) { AccessController.doPrivileged( new PrivilegedAction() { @Override public Void run() { listeners[serviceIndex].configurationEvent(getConfigurationEvent(serviceReference)); return null; } }, BaseTracker.getAccessControlContext(listenerProvider[serviceIndex]) ); } else { listeners[serviceIndex].configurationEvent(getConfigurationEvent(serviceReference)); } } catch ( Throwable t ) { Log.logger.log( LogService.LOG_ERROR, "Unexpected problem delivering configuration event to {0}", new Object[] { listenerReferences[serviceIndex], t } ); } finally { this.listeners[serviceIndex] = null; } } } } public void setCoordinator(final Object service) { this.coordinator = service; } }