blob: e9e4620814abc7bbbae0bf039f749a29e1515a3e [file] [log] [blame]
/*
* 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.security.SecureRandom;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.file.FilePersistenceManager;
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.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
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.SynchronousConfigurationListener;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* The <code>ConfigurationManager</code> is the central class in this
* implementation of the Configuration Admin Service Specification. As such it
* has the following tasks:
* <ul>
* <li>It is a <code>BundleActivator</code> which is called when the bundle
* is started and stopped.
* <li>It is a <code>BundleListener</code> which gets informed when the
* states of bundles change. Mostly this is needed to unbind any bound
* configuration in case a bundle is uninstalled.
* <li>It is a <code>ServiceListener</code> which gets informed when
* <code>ManagedService</code> and <code>ManagedServiceFactory</code>
* 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.
* <li>A {@link ConfigurationAdminFactory} instance is registered as the
* <code>ConfigurationAdmin</code> service.
* <li>A {@link FilePersistenceManager} instance is registered as a default
* {@link PersistenceManager}.
* <li>Last but not least this instance manages all tasks laid out in the
* specification such as maintaining configuration, taking care of configuration
* events, etc.
* </ul>
* <p>
* The default {@link FilePersistenceManager} is configured with a configuration
* location taken from the <code>felix.cm.dir</code> framework property. If
* this property is not set the <code>config</code> directory in the current
* working directory as specified in the <code>user.dir</code> system property
* is used.
*/
public class ConfigurationManager implements BundleActivator, BundleListener
{
/**
* The name of the bundle context property defining the location for the
* configuration files (value is "felix.cm.dir").
*
* @see #start(BundleContext)
*/
public static final String CM_CONFIG_DIR = "felix.cm.dir";
/**
* The name of the bundle context property defining the maximum log level
* (value is "felix.cm.loglevel"). The log level setting is only used if
* there is no OSGi LogService available. Otherwise this setting is ignored.
* <p>
* This value of this property is expected to be an integer number
* corresponding to the log level values of the OSGi LogService. That is 1
* for errors, 2 for warnings, 3 for informational messages and 4 for debug
* messages. The default value is 2, such that only warnings and errors are
* logged in the absence of a LogService.
*/
public static final String CM_LOG_LEVEL = "felix.cm.loglevel";
// The name of the LogService (not using the class, which might be missing)
private static final String LOG_SERVICE_NAME = "org.osgi.service.log.LogService";
private static final int CM_LOG_LEVEL_DEFAULT = 2;
// random number generator to create configuration PIDs for factory
// configurations
private static Random numberGenerator;
// the BundleContext of the Configuration Admin Service bundle
BundleContext bundleContext;
// the service registration of the default file persistence manager
private volatile ServiceRegistration filepmRegistration;
// the service registration of the configuration admin
private volatile ServiceRegistration configurationAdminRegistration;
// the ServiceTracker to emit log services (see log(int, String, Throwable))
private ServiceTracker logTracker;
// 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;
// PersistenceManager services
private ServiceTracker persistenceManagerTracker;
// 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 actual list of {@link PersistenceManager persistence managers} to use
* when looking for configuration data. This list is built from the
* {@link #persistenceManagerMap}, which is ordered according to the
* {@link RankingComparator}.
*/
private CachingPersistenceManagerProxy[] persistenceManagers;
// the persistenceManagerTracker.getTrackingCount when the
// persistenceManagers were last got
private int pmtCount;
// the cache of Factory instances mapped by their factory PID
private final HashMap<String, Factory> factories = new HashMap<String, Factory>();
// the cache of Configuration instances mapped by their PID
// have this always set to prevent NPE on bundle shutdown
private final HashMap<String, ConfigurationImpl> configurations = new HashMap<String, ConfigurationImpl>();
/**
* The map of dynamic configuration bindings. This maps the
* PID of the dynamically bound configuration or factory to its bundle
* location.
* <p>
* 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.
* <p>
* The map is written to persistence on each change.
*/
private DynamicBindings dynamicBindings;
// the maximum log level when no LogService is available
private int logLevel = CM_LOG_LEVEL_DEFAULT;
// 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;
public void start( BundleContext bundleContext )
{
// track the log service using a ServiceTracker
logTracker = new ServiceTracker( bundleContext, LOG_SERVICE_NAME , null );
logTracker.open();
// assign the log level
String logLevelProp = bundleContext.getProperty( CM_LOG_LEVEL );
if ( logLevelProp == null )
{
logLevel = CM_LOG_LEVEL_DEFAULT;
}
else
{
try
{
logLevel = Integer.parseInt( logLevelProp );
}
catch ( NumberFormatException nfe )
{
logLevel = CM_LOG_LEVEL_DEFAULT;
}
}
// set up some fields
this.bundleContext = bundleContext;
// configurationlistener support
configurationListenerTracker = new ServiceTracker( bundleContext, ConfigurationListener.class.getName(), null );
configurationListenerTracker.open();
syncConfigurationListenerTracker = new ServiceTracker( bundleContext,
SynchronousConfigurationListener.class.getName(), null );
syncConfigurationListenerTracker.open();
// initialize the asynchonous updater thread
ThreadGroup tg = new ThreadGroup( "Configuration Admin Service" );
tg.setDaemon( true );
this.updateThread = new UpdateThread( this, tg, "CM Configuration Updater" );
this.eventThread = new UpdateThread( this, tg, "CM Event Dispatcher" );
// set up the location (might throw IllegalArgumentException)
try
{
FilePersistenceManager fpm = new FilePersistenceManager( bundleContext, bundleContext
.getProperty( CM_CONFIG_DIR ) );
Hashtable props = new Hashtable();
props.put( Constants.SERVICE_PID, fpm.getClass().getName() );
props.put( Constants.SERVICE_DESCRIPTION, "Platform Filesystem Persistence Manager" );
props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
props.put( Constants.SERVICE_RANKING, new Integer( Integer.MIN_VALUE ) );
filepmRegistration = bundleContext.registerService( PersistenceManager.class.getName(), fpm, props );
// setup dynamic configuration bindings
dynamicBindings = new DynamicBindings( bundleContext, fpm );
}
catch ( IOException ioe )
{
log( LogService.LOG_ERROR, "Failure setting up dynamic configuration bindings", ioe );
}
catch ( IllegalArgumentException iae )
{
log( LogService.LOG_ERROR, "Cannot create the FilePersistenceManager", iae );
}
// register as bundle and service listener
handleBundleEvents = true;
bundleContext.addBundleListener( this );
// get all persistence managers to begin with
pmtCount = 1; // make sure to get the persistence managers at least once
persistenceManagerTracker = new ServiceTracker( bundleContext, PersistenceManager.class.getName(), null );
persistenceManagerTracker.open();
// 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 );
Hashtable props = new Hashtable();
props.put( Constants.SERVICE_PID, "org.apache.felix.cm.ConfigurationAdmin" );
props.put( Constants.SERVICE_DESCRIPTION, "Configuration Admin Service Specification 1.2 Implementation" );
props.put( Constants.SERVICE_VENDOR, "Apache Software Foundation" );
configurationAdminRegistration = bundleContext.registerService( ConfigurationAdmin.class.getName(), 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();
}
public void stop( BundleContext bundleContext )
{
// 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;
// don't care for PersistenceManagers any more
persistenceManagerTracker.close();
// shutdown the file persistence manager
final ServiceRegistration filePmReg = filepmRegistration;
filepmRegistration = null;
if ( filePmReg != null )
{
filePmReg.unregister();
}
// stop listening for events
bundleContext.removeBundleListener( this );
if ( configurationListenerTracker != null )
{
configurationListenerTracker.close();
}
if ( syncConfigurationListenerTracker != null )
{
syncConfigurationListenerTracker.close();
}
if ( logTracker != null )
{
logTracker.close();
}
// just ensure the configuration cache is empty
synchronized ( configurations )
{
configurations.clear();
}
// just ensure the factory cache is empty
synchronized ( factories )
{
factories.clear();
}
this.bundleContext = null;
}
/**
* Returns <code>true</code> 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() );
}
}
Factory getCachedFactory( String factoryPid )
{
synchronized ( factories )
{
return factories.get( factoryPid );
}
}
Factory[] getCachedFactories()
{
synchronized ( factories )
{
return factories.values().toArray( new Factory[factories.size()] );
}
}
void cacheFactory( Factory factory )
{
synchronized ( factories )
{
factories.put( factory.getFactoryPidString(), factory );
}
}
// ---------- ConfigurationAdminImpl support
void setDynamicBundleLocation( final String pid, final String location )
{
if ( dynamicBindings != null )
{
try
{
dynamicBindings.putLocation( pid, location );
}
catch ( IOException ioe )
{
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( createConfiguration( createPid( factoryPid ), factoryPid, location ) );
}
/**
* Returns a targeted configuration for the given service PID and
* the reference target service.
* <p>
* 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 <code>ServiceReference</code> to get
* configuration for.
* @return The best matching targeted configuration or <code>null</code>
* 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
// (StringBuffer for pre-1.5 API compatibility)
final StringBuffer targetedPid = new StringBuffer( 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( TargetedPID.getBundleVersion( serviceBundle ) );
names[i--] = targetedPid.toString();
targetedPid.append( '|' ).append( serviceBundle.getLocation() );
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( serviceBundle.getLocation() );
return config;
}
// CM 1.4 / 104.13.2.2 / 104.5.3
// act as if there is no configuration
log(
LogService.LOG_DEBUG,
"Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}; calling with null",
new Object[]
{ config.getPid(), toString( target ), config.getBundleLocation() } );
}
}
}
else
{
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 <code>null</code> is returned.
*
* @param pid The PID for which to return the configuration
* @return The configuration or <code>null</code> if non exists
* @throws IOException If an error occurrs reading from a persistence
* manager.
*/
ConfigurationImpl getConfiguration( String pid ) throws IOException
{
ConfigurationImpl config = getCachedConfiguration( pid );
if ( config != null )
{
log( LogService.LOG_DEBUG, "Found cached configuration {0} bound to {1}", new Object[]
{ pid, config.getBundleLocation() } );
config.ensureFactoryConfigPersisted();
return config;
}
PersistenceManager[] pmList = getPersistenceManagers();
for ( int i = 0; i < pmList.length; i++ )
{
if ( pmList[i].exists( pid ) )
{
Dictionary props = pmList[i].load( pid );
config = new ConfigurationImpl( this, pmList[i], props );
log( LogService.LOG_DEBUG, "Found existing configuration {0} bound to {1}", new Object[]
{ pid, config.getBundleLocation() } );
return cacheConfiguration( config );
}
}
// neither the cache nor any persistence manager has configuration
return null;
}
/**
* Creates a regular (non-factory) configuration for the given PID
* setting the bundle location accordingly.
* <p>
* 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 <code>null</code> 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 = createConfiguration( 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( LogService.LOG_DEBUG, "Listing configurations matching {0}", new Object[]
{ filterString } );
List configList = new ArrayList();
CachingPersistenceManagerProxy[] pmList = getPersistenceManagers();
for ( int i = 0; i < pmList.length; i++ )
{
Enumeration configs = pmList[i].getDictionaries( filter );
while ( configs.hasMoreElements() )
{
final Dictionary config = ( Dictionary ) configs.nextElement();
// 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(
LogService.LOG_DEBUG,
"Omitting configuration {0}: No permission for bundle {1} on configuration bound to {2}",
new Object[]
{ pid, configurationAdmin.getBundle().getLocation(),
config.get( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) } );
continue;
}
// ensure the service.pid and returned a cached config if available
ConfigurationImpl cfg = getCachedConfiguration( pid );
if ( cfg == null )
{
cfg = new ConfigurationImpl( this, pmList[i], config );
}
// FELIX-611: Ignore configuration objects without props
if ( !cfg.isNew() )
{
log( LogService.LOG_DEBUG, "Adding configuration {0}", new Object[]
{ pid } );
configList.add( cfg );
}
else
{
log( LogService.LOG_DEBUG, "Omitting configuration {0}: Is new", new Object[]
{ pid } );
}
}
}
if ( configList.size() == 0 )
{
return null;
}
return ( ConfigurationImpl[] ) 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() );
updateThread.schedule( new DeleteConfiguration( config ) );
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() );
}
updateThread.schedule( new UpdateConfiguration( config ) );
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() )
{
updateThread.schedule( new LocationChanged( config, oldLocation ) );
log( LogService.LOG_DEBUG, "LocationChanged({0}, {1}=>{2}) scheduled", new Object[]
{ config.getPid(), oldLocation, config.getBundleLocation() } );
}
else
{
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( LogService.LOG_DEBUG, "No SynchronousConfigurationListeners to send {0} event to.", new Object[]
{ syncSender.getTypeName() } );
}
// schedule asynchronous events
if ( asyncSender.hasConfigurationEventListeners() )
{
eventThread.schedule( asyncSender );
}
else
{
log( LogService.LOG_DEBUG, "No ConfigurationListeners to send {0} event to.", new Object[]
{ asyncSender.getTypeName() } );
}
}
// ---------- BundleListener -----------------------------------------------
public void bundleChanged( BundleEvent event )
{
if ( event.getType() == BundleEvent.UNINSTALLED && handleBundleEvents )
{
final String location = event.getBundle().getLocation();
// 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 CachingPersistenceManagerProxy[] getPersistenceManagers()
{
int currentPmtCount = persistenceManagerTracker.getTrackingCount();
if ( persistenceManagers == null || currentPmtCount > pmtCount )
{
List pmList = new ArrayList();
CachingPersistenceManagerProxy[] pm;
ServiceReference[] refs = persistenceManagerTracker.getServiceReferences();
if ( refs == null || refs.length == 0 )
{
pm = new CachingPersistenceManagerProxy[0];
}
else
{
// sort the references according to the cmRanking property
if ( refs.length > 1 )
{
Arrays.sort( refs, RankingComparator.SRV_RANKING );
}
// create the service array from the sorted set of referenecs
for ( int i = 0; i < refs.length; i++ )
{
Object service = persistenceManagerTracker.getService( refs[i] );
if ( service != null )
{
pmList.add( new CachingPersistenceManagerProxy( ( PersistenceManager ) service ) );
}
}
pm = ( CachingPersistenceManagerProxy[] ) pmList.toArray( new CachingPersistenceManagerProxy[pmList.size()] );
}
pmtCount = currentPmtCount;
persistenceManagers = pm;
}
return persistenceManagers;
}
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
{
ServiceReference[] refs = context.getServiceReferences( ConfigurationAdmin.class.getName(), null );
if ( refs != null )
{
for ( int i = 0; i < refs.length; i++ )
{
if ( refs[i].getBundle().getBundleId() == context.getBundle().getBundleId() )
{
return refs[i];
}
}
}
}
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 <code>null</code> 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 <code>null</code> 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 <code>ServiceReference</code> to the service
* to be configured.
* @param factory <code>true</code> If the service is considered to
* be a <code>ManagedServiceFactory</code>. Otherwise the service
* is considered to be a <code>ManagedService</code>.
*/
public void configure( String[] pid, ServiceReference sr, final boolean factory, final ConfigurationMap<?> configs )
{
if ( this.isLogEnabled( LogService.LOG_DEBUG ) )
{
this.log( LogService.LOG_DEBUG, "configure(ManagedService {0})", new Object[]
{ toString( sr ) } );
}
Runnable r;
if ( factory )
{
r = new ManagedServiceFactoryUpdate( pid, sr, configs );
}
else
{
r = new ManagedServiceUpdate( pid, sr, configs );
}
updateThread.schedule( r );
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 <code>factoryPid</code> parameter is <code>null</code>.
*
* @param pid
* The PID of the new configuration object. Must not be
* <code>null</code>.
* @param factoryPid
* The factory PID of the new configuration. Not
* <code>null</code> if the new configuration object belongs to a
* factory. The configuration object will not be persisted if
* this parameter is not <code>null</code>.
* @param bundleLocation
* The bundle location of the bundle to which the configuration
* belongs or <code>null</code> 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.
*/
ConfigurationImpl createConfiguration( String pid, String factoryPid, String bundleLocation ) throws IOException
{
log( LogService.LOG_DEBUG, "createConfiguration({0}, {1}, {2})", new Object[]
{ pid, factoryPid, bundleLocation } );
return new ConfigurationImpl( this, getPersistenceManagers()[0], 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 targetting.
* @param target The <code>ServiceReference</code> 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
* <code>rawFactoryPid</code>. Other instances are only included
* if existing.
* @throws IOException If an error occurrs reading any of the
* {@link Factory} instances from persistence
*/
List<Factory> getTargetedFactories( final String rawFactoryPid, final ServiceReference target ) throws IOException
{
LinkedList<Factory> factories = new LinkedList<Factory>();
final Bundle serviceBundle = target.getBundle();
if ( serviceBundle != null )
{
// for pre-1.5 API compatibility
final StringBuffer targetedPid = new StringBuffer( rawFactoryPid );
factories.add( getOrCreateFactory( targetedPid.toString() ) );
targetedPid.append( '|' ).append( serviceBundle.getSymbolicName() );
Factory f = getFactory( targetedPid.toString() );
if ( f != null )
{
factories.add( 0, f );
}
targetedPid.append( '|' ).append( TargetedPID.getBundleVersion( serviceBundle ) );
f = getFactory( targetedPid.toString() );
if ( f != null )
{
factories.add( 0, f );
}
targetedPid.append( '|' ).append( serviceBundle.getLocation() );
f = getFactory( targetedPid.toString() );
if ( f != null )
{
factories.add( 0, f );
}
}
return factories;
}
/**
* Gets the factory with the exact identifier from the cached or from
* the persistence managers. If no factory exists already one is
* created and cached.
*
* @param factoryPid The PID of the {@link Factory} to return
* @return The existing or newly created {@link Factory}
* @throws IOException If an error occurrs reading the factory from
* a {@link PersistenceManager}
*/
Factory getOrCreateFactory( String factoryPid ) throws IOException
{
Factory factory = getFactory( factoryPid );
if ( factory != null )
{
return factory;
}
return createFactory( factoryPid );
}
/**
* Gets the factory with the exact identifier from the cached or from
* the persistence managers. If no factory exists <code>null</code>
* is returned.
*
* @param factoryPid The PID of the {@link Factory} to return
* @return The existing {@link Factory} or <code>null</code>
* @throws IOException If an error occurrs reading the factory from
* a {@link PersistenceManager}
*/
Factory getFactory( String factoryPid ) throws IOException
{
// check for cached factory
Factory factory = getCachedFactory( factoryPid );
if ( factory != null )
{
return factory;
}
// try to load factory from persistence
PersistenceManager[] pmList = getPersistenceManagers();
for ( int i = 0; i < pmList.length; i++ )
{
if ( Factory.exists( pmList[i], factoryPid ) )
{
factory = Factory.load( this, pmList[i], factoryPid );
cacheFactory( factory );
return factory;
}
}
// no existing factory
return null;
}
/**
* Creates a new factory with the given <code>factoryPid</code>.
*/
Factory createFactory( String factoryPid )
{
Factory factory = new Factory( this, getPersistenceManagers()[0], factoryPid );
cacheFactory( factory );
return factory;
}
/**
* Calls the registered configuration plugins on the given configuration
* properties from the given configuration object.
* <p>
* The plugins to be called are selected as <code>ConfigurationPlugin</code>
* services registered with a <code>cm.target</code> property set to
* <code>*</code> or the factory PID of the configuration (for factory
* configurations) or the PID of the configuration (for non-factory
* configurations).
*
* @param props The configuraiton properties run through the registered
* ConfigurationPlugin services. This must not be
* <code>null</code>.
* @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-<code>null</code>
* 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 )
{
try
{
plugin.modifyConfiguration( sr, props );
}
catch ( Throwable t )
{
log( LogService.LOG_ERROR, "Unexpected problem calling configuration plugin {0}", new Object[]
{ toString( 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 */
StringBuffer buf = new StringBuffer( 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();
}
public boolean isLogEnabled( int level )
{
return level <= logLevel;
}
public void log( int level, String format, Object[] args )
{
if ( isLogEnabled( level ) )
{
Throwable throwable = null;
String message = format;
if ( args != null && args.length > 0 )
{
if ( args[args.length - 1] instanceof Throwable )
{
throwable = ( Throwable ) args[args.length - 1];
}
message = MessageFormat.format( format, args );
}
log( level, message, throwable );
}
}
public void log( int level, String message, Throwable t )
{
// log using the LogService if available
Object log = logTracker.getService();
if ( log != null )
{
( ( LogService ) log ).log( getServiceReference(), level, message, t );
return;
}
// Otherwise only log if more serious than the configured level
if ( isLogEnabled( level ) )
{
String code;
switch ( level )
{
case LogService.LOG_INFO:
code = "*INFO *";
break;
case LogService.LOG_WARNING:
code = "*WARN *";
break;
case LogService.LOG_ERROR:
code = "*ERROR*";
break;
case LogService.LOG_DEBUG:
default:
code = "*DEBUG*";
}
System.err.println( code + " " + message );
if ( t != null )
{
t.printStackTrace( System.err );
}
}
}
public static String toString( ServiceReference ref )
{
String[] ocs = ( String[] ) ref.getProperty( "objectClass" );
StringBuffer buf = new StringBuffer( "[" );
for ( int i = 0; i < ocs.length; i++ )
{
buf.append( ocs[i] );
if ( i < ocs.length - 1 )
buf.append( ", " );
}
buf.append( ", id=" ).append( ref.getProperty( Constants.SERVICE_ID ) );
Bundle provider = ref.getBundle();
if ( provider != null )
{
buf.append( ", bundle=" ).append( provider.getBundleId() );
buf.append( '/' ).append( provider.getLocation() );
}
else
{
buf.append( ", unregistered" );
}
buf.append( "]" );
return buf.toString();
}
/**
* Checks whether the bundle is allowed to receive the configuration
* with the given location binding.
* <p>
* This method implements the logic defined CM 1.4 / 104.4.1:
* <ul>
* <li>If the location is <code>null</code> (the configuration is not
* bound yet), assume the bundle is allowed</li>
* <li>If the location is a single location (no leading "?"), require
* the bundle's location to match</li>
* <li>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.</li>
* </ul>
*/
boolean canReceive( final Bundle bundle, final String location )
{
if ( location == null )
{
log( LogService.LOG_DEBUG, "canReceive=true; bundle={0}; configuration=(unbound)", new Object[]
{ bundle.getLocation() } );
return true;
}
else if ( location.startsWith( "?" ) )
{
// multi-location
if ( System.getSecurityManager() != null )
{
final boolean hasPermission = bundle.hasPermission( new ConfigurationPermission( location,
ConfigurationPermission.TARGET ) );
log( LogService.LOG_DEBUG, "canReceive={0}: bundle={1}; configuration={2} (SecurityManager check)",
new Object[]
{ new Boolean( hasPermission ), bundle.getLocation(), location } );
return hasPermission;
}
log( LogService.LOG_DEBUG, "canReceive=true; bundle={0}; configuration={1} (no SecurityManager)",
new Object[]
{ bundle.getLocation(), location } );
return true;
}
else
{
// single location, must match
final boolean hasPermission = location.equals( bundle.getLocation() );
log( LogService.LOG_DEBUG, "canReceive={0}: bundle={1}; configuration={2}", new Object[]
{ new Boolean( hasPermission ), bundle.getLocation(), location } );
return hasPermission;
}
}
// ---------- inner classes
/**
* The <code>ManagedServiceUpdate</code> updates a freshly registered
* <code>ManagedService</code> 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;
}
public void run()
{
for ( String pid : this.pids )
{
try
{
final ConfigurationImpl config = getTargetedConfiguration( pid, this.sr );
provide( pid, config );
}
catch ( IOException ioe )
{
log( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[]
{ pid, ioe } );
}
catch ( Exception e )
{
log( LogService.LOG_ERROR, "Unexpected problem providing configuration {0} to service {1}",
new Object[]
{ pid, ConfigurationManager.toString( 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( 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 <code>ManagedServiceFactoryUpdate</code> updates a freshly
* registered <code>ManagedServiceFactory</code> 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;
}
public void run()
{
for ( String factoryPid : this.factoryPids )
{
List<Factory> factories = null;
try
{
factories = getTargetedFactories( factoryPid, sr );
for ( Factory factory : factories )
{
for ( Iterator pi = factory.getPIDs().iterator(); pi.hasNext(); )
{
final String pid = ( String ) pi.next();
ConfigurationImpl cfg;
try
{
cfg = getConfiguration( pid );
}
catch ( IOException ioe )
{
log( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[]
{ pid, ioe } );
continue;
}
// sanity check on the configuration
if ( cfg == null )
{
log( LogService.LOG_ERROR,
"Configuration {0} referred to by factory {1} does not exist", new Object[]
{ pid, factoryPid } );
factory.removePID( pid );
factory.storeSilently();
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( LogService.LOG_ERROR, "Ignoring new configuration pid={0}", new Object[]
{ pid } );
continue;
}
/*
* this code would catch targeted factory PIDs;
* since this is not expected any way, we can
* leave this out
*/
/*
else if ( !factoryPid.equals( cfg.getFactoryPid() ) )
{
log( LogService.LOG_ERROR,
"Configuration {0} referred to by factory {1} seems to belong to factory {2}",
new Object[]
{ pid, factoryPid, cfg.getFactoryPid() } );
factory.removePID( pid );
factory.storeSilently();
continue;
}
*/
provide( factoryPid, cfg );
}
}
}
catch ( IOException ioe )
{
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( 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(
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( LogService.LOG_ERROR,
"Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}",
new Object[]
{ config.getPid(), ConfigurationManager.toString( sr ), config.getBundleLocation() } );
// no service, really, bail out
return;
}
// 104.4.2 Dynamic Binding
config.tryBindLocation( serviceBundle.getLocation() );
// update the service with the configuration (if non-null)
if ( rawProperties != null )
{
log( LogService.LOG_DEBUG, "{0}: Updating configuration pid={1}", new Object[]
{ ConfigurationManager.toString( 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<T> implements Runnable
{
protected final ConfigurationImpl config;
protected final long revision;
protected final Dictionary<String, ?> properties;
private BaseTracker<T> 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<T> getHelper()
{
if ( this.helper == null )
{
this.helper = ( BaseTracker<T> ) ( ( this.config.getFactoryPid() == null ) ? ConfigurationManager.this.managedServiceTracker
: ConfigurationManager.this.managedServiceFactoryTracker );
}
return this.helper;
}
protected boolean provideReplacement( ServiceReference<T> 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( LogService.LOG_ERROR, "Error loading configuration for {0}", new Object[]
{ this.config.getPid(), ioe } );
}
catch ( Exception e )
{
log( LogService.LOG_ERROR, "Unexpected problem providing configuration {0} to service {1}",
new Object[]
{ this.config.getPid(), ConfigurationManager.toString( sr ), e } );
}
}
// factory or no replacement available
return false;
}
}
/**
* The <code>UpdateConfiguration</code> is used to update
* <code>ManagedService[Factory]</code> 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 );
}
public void run()
{
log( LogService.LOG_DEBUG, "Updating configuration {0} to revision #{1}", new Object[]
{ config.getPid(), new Long( revision ) } );
final List<ServiceReference<?>> 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( LogService.LOG_DEBUG,
"Service {0} seems to be unregistered concurrently (not providing configuration)",
new Object[]
{ ConfigurationManager.toString( srList.get(0) ) } );
return;
}
config.tryBindLocation( bundle.getLocation() );
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( LogService.LOG_DEBUG,
"Service {0} seems to be unregistered concurrently (not providing configuration)",
new Object[]
{ ConfigurationManager.toString( 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( LogService.LOG_ERROR,
"Cannot use configuration {0} for {1}: No visibility to configuration bound to {2}",
new Object[]
{ config.getPid(), ConfigurationManager.toString( ref ), configBundleLocation } );
}
}
}
else if ( isLogEnabled( LogService.LOG_DEBUG ) )
{
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 <code>DeleteConfiguration</code> class is used to inform
* <code>ManagedService[Factory]</code> 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();
}
public void run()
{
List<ServiceReference<?>> srList = this.getHelper().getServices( getTargetedServicePid() );
if ( !srList.isEmpty() )
{
for (ServiceReference<?> sr : srList)
{
final Bundle srBundle = sr.getBundle();
if ( srBundle == null )
{
log( LogService.LOG_DEBUG,
"Service {0} seems to be unregistered concurrently (not removing configuration)",
new Object[]
{ ConfigurationManager.toString( 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( LogService.LOG_ERROR,
"Cannot remove configuration {0} for {1}: No visibility to configuration bound to {2}",
new Object[]
{ config.getPid(), ConfigurationManager.toString( sr ), configLocation } );
}
}
}
final TargetedPID factoryPid = config.getFactoryPid();
if ( factoryPid != null )
{
// remove the pid from the factory
final String pid = config.getPidString();
try
{
Factory factory = getOrCreateFactory( factoryPid.toString() );
factory.removePID( pid );
factory.store();
}
catch ( IOException ioe )
{
log( LogService.LOG_ERROR, "Failed removing {0} from the factory {1}", new Object[]
{ pid, factoryPid, ioe } );
}
}
}
@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;
}
public void run()
{
List<ServiceReference<?>> srList = this.getHelper().getServices( getTargetedServicePid() );
if ( !srList.isEmpty() )
{
for (final ServiceReference<?> sr : srList)
{
final Bundle srBundle = sr.getBundle();
if ( srBundle == null )
{
log( LogService.LOG_DEBUG,
"Service {0} seems to be unregistered concurrently (not processing)", new Object[]
{ ConfigurationManager.toString( 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( srBundle.getLocation() );
}
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( LogService.LOG_DEBUG, "Configuration {0} revoked from {1} (no more visibility)",
new Object[]
{ config.getPid(), ConfigurationManager.toString( sr ) } );
}
}
else if ( !wasVisible && isVisible )
{
// call updated method
this.getHelper().provideConfiguration( sr, this.config.getPid(), this.config.getFactoryPid(),
this.properties, this.revision, null );
log( LogService.LOG_DEBUG, "Configuration {0} provided to {1} (new visibility)", new Object[]
{ config.getPid(), ConfigurationManager.toString( sr ) } );
}
else
{
// same visibility as before
log( LogService.LOG_DEBUG, "Unmodified visibility to configuration {0} for {1}", new Object[]
{ config.getPid(), ConfigurationManager.toString( 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 "<UNKNOWN(" + type + ")>";
}
}
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()
{
if ( event == null )
{
this.event = new ConfigurationEvent( getServiceReference(), 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( LogService.LOG_DEBUG, "Sending {0} event for {1} to {2}", new Object[]
{ getTypeName(), pid, ConfigurationManager.toString( listenerReferences[serviceIndex] ) } );
try
{
listeners[serviceIndex].configurationEvent( getConfigurationEvent() );
}
catch ( Throwable t )
{
log( LogService.LOG_ERROR, "Unexpected problem delivering configuration event to {0}", new Object[]
{ ConfigurationManager.toString( listenerReferences[serviceIndex] ), t } );
}
finally
{
this.listeners[serviceIndex] = null;
}
}
}
}
}