blob: c8e6446f58d726552785da66499af4c1d77d38d7 [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.scr.impl.manager;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.felix.scr.impl.inject.ComponentMethods;
import org.apache.felix.scr.impl.logger.ComponentLogger;
import org.apache.felix.scr.impl.metadata.ComponentMetadata;
import org.apache.felix.scr.impl.metadata.ServiceMetadata.Scope;
import org.apache.felix.scr.impl.metadata.TargetedPID;
import org.osgi.framework.Constants;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.log.LogService;
import org.osgi.util.promise.Deferred;
import org.osgi.util.promise.Promise;
import org.osgi.util.promise.Promises;
/**
* The <code>ConfigurableComponentHolder</code> class is a
* {@link ComponentHolder} for automatically configured components instances
* that may or may not be configured through Config Admin.
* <p>
* The holder copes with three situations:
* <ul>
* <li>No configuration is available for the held component. That is there is
* no configuration whose <code>service.pid</code> or
* <code>service.factoryPid</code> equals the component name.</li>
* <li>A singleton configuration is available whose <code>service.pid</code>
* equals the component name.</li>
* <li>One or more factory configurations exist whose
* <code>service.factoryPid</code> equals the component name.</li>
* </ul>
*/
public abstract class ConfigurableComponentHolder<S> implements ComponentHolder<S>, ComponentContainer<S>
{
/**
* The activator owning the per-bundle components
*/
private final ComponentActivator m_activator;
/**
* The {@link ComponentMetadata} describing the held component(s)
*/
private final ComponentMetadata m_componentMetadata;
/** the targeted pids corresponding to the pids specified in the config metadata, except possibly for the single
* factory pid
*/
private final TargetedPID[] m_targetedPids;
private final Long[] m_changeCount;
private final Map<String, Long> m_factoryChangeCount = new HashMap<>();
/**
* the index in metadata.getConfigurationPid() of the base factory pid, if any. Each component created from a factory configuration
* might have a different targeted pid.
*/
private volatile Integer m_factoryPidIndex;
/**
* the non-factory configurations shared between all instances.
*/
private final Dictionary<String, Object>[] m_configurations;
/**
* the factory configurations indexed by pid (which cannot be a TargetedPID since it's generated by CA). We have to track these since
* other required configs may not yet be present so we can't create the component manager yet.
*/
private final Map<String, Dictionary<String, Object>> m_factoryConfigurations = new HashMap<>();
/**
* Each factory config may be from a different TargetedPID (sharing the same base service pid, but with different level of detail)
*/
private final Map<String, TargetedPID> m_factoryTargetedPids = new HashMap<>();
/**
* A map of components configured with factory configuration. The indices
* are the PIDs (<code>service.pid</code>) of the configuration objects.
* The values are the {@link SingleComponentManager<S> component instances}
* created on behalf of the configurations.
*/
private final Map<String, AbstractComponentManager<S>> m_components;
/**
* The special component used if there is no configuration or a singleton
* configuration. This field is only <code>null</code> once all components
* held by this holder have been disposed of by
* {@link #disposeComponents(int)} and is first created in the constructor.
* As factory configurations are provided this instance may be configured
* or "deconfigured".
* <p>
* Expected invariants:
* <ul>
* <li>This field is only <code>null</code> after disposal of all held
* components</li>
* <li>The {@link #m_components} map is empty or the component pointed to
* by this field is also contained in the map</li>
* <ul>
*/
private volatile AbstractComponentManager<S> m_singleComponent;
/**
* Whether components have already been enabled by calling the
* {@link #enableComponents(boolean)} method. If this field is <code>true</code>
* component instances created per configuration by the
* {@link #configurationUpdated(TargetedPID, TargetedPID, Dictionary, long)} method are also
* enabled. Otherwise they are not enabled immediately.
*/
private volatile boolean m_enabled;
private final Object enableLock = new Object();
private volatile Promise<Void> m_enablePromise;
private volatile Promise<Void> m_disablePromise = Promises.resolved(null);
private final ComponentMethods<S> m_componentMethods;
private final ComponentLogger logger;
public ConfigurableComponentHolder( final ComponentActivator activator,
final ComponentMetadata metadata,
final ComponentLogger logger)
{
this.logger = logger;
this.m_activator = activator;
this.m_componentMetadata = metadata;
final int pidCount = metadata.getConfigurationPid().size();
this.m_targetedPids = new TargetedPID[pidCount];
this.m_configurations = new Dictionary[pidCount];
this.m_changeCount = new Long[pidCount];
this.m_components = new HashMap<>();
this.m_componentMethods = createComponentMethods();
this.m_enabled = false;
}
protected abstract ComponentMethods<S> createComponentMethods();
protected ComponentMethods<S> getComponentMethods() {
return m_componentMethods;
}
protected AbstractComponentManager<S> createComponentManager(boolean factoryConfiguration)
{
AbstractComponentManager<S> manager;
if ( m_componentMetadata.isFactory() )
{
//TODO is there any check to make sure factory component factories are enabled before creating them?
if ( !m_componentMetadata.isObsoleteFactoryComponentFactory() || !factoryConfiguration )
{
manager = new ComponentFactoryImpl<>(this, m_componentMethods);
}
else
{
manager = new SingleComponentManager<>(this, m_componentMethods, true );
}
}
else if ( m_componentMetadata.getServiceScope() == Scope.bundle )
{
manager = new ServiceFactoryComponentManager<>( this, m_componentMethods );
}
else if ( m_componentMetadata.getServiceScope() == Scope.prototype )
{
manager = PSFLoader.newPSFComponentManager(this, m_componentMethods);
}
else
{
//immediate or delayed
manager = new SingleComponentManager<>( this, m_componentMethods );
}
return manager;
}
private static class PSFLoader
{
static <S> AbstractComponentManager<S> newPSFComponentManager(ConfigurableComponentHolder<S> holder, ComponentMethods methods)
{
return new PrototypeServiceFactoryComponentManager<>( holder, methods );
}
}
@Override
public final ComponentActivator getActivator()
{
return m_activator;
}
@Override
public final ComponentMetadata getComponentMetadata()
{
return m_componentMetadata;
}
/**
* The configuration with the given <code>pid</code>
* (<code>service.pid</code> of the configuration object) is deleted.
* <p>
* The following situations are supported:
* <ul>
* <li>The configuration was a singleton configuration (pid equals the
* component name). In this case the internal component map is empty and
* the single component has been configured by the singleton configuration
* and is no "deconfigured".</li>
* <li>A factory configuration object has been deleted and the configured
* object is set as the single component. If the single component held the
* last factory configuration object, it is deconfigured. Otherwise the
* single component is disposed off and replaced by another component in
* the map of existing components.</li>
* <li>A factory configuration object has been deleted and the configured
* object is not set as the single component. In this case the component is
* simply disposed off and removed from the internal map.</li>
* </ul>
*/
@Override
public void configurationDeleted( final TargetedPID pid, TargetedPID factoryPid )
{
logger.log( LogService.LOG_DEBUG, "ImmediateComponentHolder configuration deleted for pid {0}", null, pid);
// component to deconfigure or dispose of
final Map<AbstractComponentManager<S>, Map<String, Object>> scms = new HashMap<>();
boolean reconfigure = false;
synchronized ( m_components )
{
if (factoryPid != null) {
checkFactoryPidIndex(factoryPid);
String servicePid = pid.getServicePid();
m_factoryTargetedPids.remove(servicePid);
m_factoryChangeCount.remove(servicePid);
m_factoryConfigurations.remove(servicePid);
AbstractComponentManager<S> scm = m_components.remove(servicePid);
if ( m_factoryConfigurations.isEmpty() )
{
m_factoryPidIndex = null;
}
if ( !m_enabled || scm == null )
{
return;
}
reconfigure = m_componentMetadata.isConfigurationOptional() && m_components.isEmpty();
if ( reconfigure )
{
m_singleComponent = scm;
scms.put( scm, mergeProperties(null) );
}
else
{
scms.put( scm, null );
}
}
else
{
//singleton pid
int index = getSingletonPidIndex(pid);
m_targetedPids[index] = null;
m_changeCount[index] = null;
m_configurations[index] = null;
if ( !m_enabled )
{
return;
}
reconfigure = m_componentMetadata.isConfigurationOptional();
if ( m_factoryPidIndex == null)
{
if ( m_singleComponent != null ) {
if (reconfigure) {
scms.put(m_singleComponent, mergeProperties(null));
} else {
scms.put(m_singleComponent, null);
m_singleComponent = null;
}
}
}
else
{
if (reconfigure) {
for (Map.Entry<String, AbstractComponentManager<S>> entry : m_components.entrySet()) {
scms.put(entry.getValue(), mergeProperties(entry.getKey()));
}
}
else
{
for (Map.Entry<String, AbstractComponentManager<S>> entry : m_components.entrySet()) {
scms.put(entry.getValue(), null );
}
m_components.clear();
}
}
}
}
for ( Map.Entry<AbstractComponentManager<S>,Map<String, Object>> entry: scms.entrySet())
{
if ( reconfigure ) {
entry.getKey().reconfigure( entry.getValue(), true, factoryPid);
} else {
entry.getKey().dispose(ComponentConstants.DEACTIVATION_REASON_CONFIGURATION_DELETED);
}
}
}
/**
* Configures a component with the given configuration. This configuration
* update may happen in various situations:
* <ul>
* <li>The <code>pid</code> equals the component name. Hence we have a
* singleton configuration for the single component held by this holder</li>
* <li>The configuration is a factory configuration and is the first
* configuration provided. In this case the single component is provided
* with the configuration and also stored in the map.</li>
* <li>The configuration is a factory configuration but not the first. In
* this case a new component is created, configured and stored in the map</li>
* </ul>
* @return true if a new configuration was created, false otherwise.
*
* TODO there are now 3 states..... still not satisfied, existing, and new
*/
@Override
public boolean configurationUpdated( TargetedPID pid, TargetedPID factoryPid, final Dictionary<String, Object> props, long changeCount )
{
logger.log(LogService.LOG_DEBUG,
"ConfigurableComponentHolder configuration updated for pid {0} with change count {1}", null, pid,
changeCount);
// component to update or create
final Map<AbstractComponentManager<S>, Map<String, Object>> scms = new HashMap<>();
boolean created = false;
synchronized (m_components) {
//Find or create the component manager, or return if not satisfied.
if (factoryPid != null) {
checkFactoryPidIndex(factoryPid);
Long oldChangeCount = m_factoryChangeCount.get(pid.getServicePid());
TargetedPID oldTargetedPID = m_factoryTargetedPids.get(pid.getServicePid());
if (oldChangeCount != null && changeCount <= oldChangeCount && factoryPid.equals(oldTargetedPID)) {
return false;
}
m_factoryChangeCount.put(pid.getServicePid(), changeCount);
m_factoryConfigurations.put(pid.getServicePid(), props);
m_factoryTargetedPids.put(pid.getServicePid(), factoryPid);
if (m_enabled && isSatisfied()) {
if (m_singleComponent != null && !m_componentMetadata.isObsoleteFactoryComponentFactory()) {
AbstractComponentManager<S> scm = m_singleComponent;
scms.put( scm, mergeProperties( pid.getServicePid() ) );
m_singleComponent = null;
m_components.put(pid.getServicePid(), scm);
} else if (m_components.containsKey(pid.getServicePid())) {
scms.put( m_components.get(pid.getServicePid()), mergeProperties( pid.getServicePid()) );
} else {
AbstractComponentManager<S> scm = createComponentManager(true);
m_components.put(pid.getServicePid(), scm);
scms.put( scm, mergeProperties( pid.getServicePid()) );
created = true;
}
} else {
return false;
}
} else {
//singleton pid
int index = getSingletonPidIndex(pid);
if (m_changeCount[index] != null && changeCount <= m_changeCount[index] && pid.equals(m_targetedPids[index])) {
return false;
}
m_changeCount[index] = changeCount;
m_targetedPids[index] = pid;
m_configurations[index] = props;
if (m_enabled && isSatisfied()) {
if (m_singleComponent != null) {
scms.put( m_singleComponent, mergeProperties( pid.getServicePid() ) );
}
else if ( m_factoryPidIndex != null)
{
for (Map.Entry<String, AbstractComponentManager<S>> entry: m_components.entrySet())
{
scms.put(entry.getValue(), mergeProperties( entry.getKey()));
}
}
else
{
m_singleComponent = createComponentManager(false);
scms.put( m_singleComponent, mergeProperties( pid.getServicePid() ) );
created = true;
}
} else {
return false;
}
}
}
// we have the icm.
//properties is all the configs merged together (without any possible component factory info.
final boolean enable = created && m_enabled;// TODO WTF?? && getComponentMetadata().isEnabled();
for ( Map.Entry<AbstractComponentManager<S>,Map<String, Object>> entry: scms.entrySet())
{
// configure the component
entry.getKey().reconfigure(entry.getValue(), false, factoryPid);
logger.log(LogService.LOG_DEBUG,
"ImmediateComponentHolder Finished configuring the dependency managers for component for pid {0} ", null,
pid );
if (enable) {
entry.getKey().enable(false);
logger.log(LogService.LOG_DEBUG,
"ImmediateComponentHolder Finished enabling component for pid {0} ", null,
pid );
} else {
logger.log(LogService.LOG_DEBUG,
"ImmediateComponentHolder Will not enable component for pid {0}: holder enabled state: {1}, metadata enabled: {2} ", null,
pid, m_enabled, m_componentMetadata.isEnabled());
}
}
return created;
}
private Map<String, Object> mergeProperties(String servicePid) {
Map<String, Object> properties = new HashMap<>(m_componentMetadata.getProperties());
List<String> pids = null;
boolean isDS13 = m_componentMetadata.getDSVersion().isDS13();
if (isDS13)
{
pids = new ArrayList<>();
if (properties.get(Constants.SERVICE_PID) instanceof String)
{
pids.add((String) properties.get(Constants.SERVICE_PID));
}
}
for (int i = 0; i < m_configurations.length; i++)
{
if ( m_factoryPidIndex != null && i == m_factoryPidIndex
&& !( m_componentMetadata.isObsoleteFactoryComponentFactory() && servicePid == null)) //obsolete special case
{
copyTo(properties, m_factoryConfigurations.get(servicePid));
if (isDS13)
{
pids.add((String) m_factoryConfigurations.get(servicePid).get(Constants.SERVICE_PID));
}
}
else if ( m_configurations[i] != null )
{
copyTo(properties, m_configurations[i]);
if (isDS13)
{
pids.add((String) m_configurations[i].get(Constants.SERVICE_PID));
}
}
}
if (isDS13 && !pids.isEmpty())
{
if ( pids.size() == 1 )
{
properties.put(Constants.SERVICE_PID, pids.get(0));
}
else
{
properties.put(Constants.SERVICE_PID, pids);
}
}
return properties;
}
private int getSingletonPidIndex(TargetedPID pid) {
int index = m_componentMetadata.getPidIndex(pid);
if (index == -1) {
logger.log(LogService.LOG_ERROR,
"Unrecognized pid {0}, expected one of {1}", null,
pid,
m_componentMetadata.getConfigurationPid() );
throw new IllegalArgumentException("Unrecognized pid "
+ pid);
}
if (m_factoryPidIndex != null && index == m_factoryPidIndex) {
logger.log(LogService.LOG_ERROR,
"singleton pid {0} supplied, but matches an existing factory pid at index: {1}",
null, pid, m_factoryPidIndex );
throw new IllegalStateException(
"Singleton pid supplied matching a previous factory pid "
+ pid);
}
return index;
}
//TODO update error messages so they make sense for deleting config too.
private void checkFactoryPidIndex(TargetedPID factoryPid) {
int index = m_componentMetadata.getPidIndex(factoryPid);
if (index == -1) {
logger.log(LogService.LOG_ERROR,
"Unrecognized factory pid {0}, expected one of {1}", null,
factoryPid,
m_componentMetadata.getConfigurationPid() );
throw new IllegalArgumentException(
"Unrecognized factory pid " + factoryPid);
}
if (m_configurations[index] != null) {
logger.log(LogService.LOG_ERROR,
"factory pid {0}, but this pid is already supplied as a singleton: {1} at index {2}", null,
factoryPid, Arrays.asList(m_targetedPids), index);
throw new IllegalStateException(
"Factory pid supplied after all non-factory configurations supplied "
+ factoryPid);
}
if (m_factoryPidIndex == null) {
m_factoryPidIndex = index;
} else if (index != m_factoryPidIndex) {
logger.log(LogService.LOG_ERROR,
"factory pid {0} supplied for index {1}, but a factory pid previously supplied at index {2}", null,
factoryPid, index, m_factoryPidIndex );
throw new IllegalStateException(
"Factory pid supplied at wrong index " + factoryPid);
}
}
protected static void copyTo( Map<String, Object> target, Dictionary<String, ?> source )
{
for ( Enumeration<String> keys = source.keys(); keys.hasMoreElements(); )
{
String key = keys.nextElement();
Object value = source.get(key);
target.put(key, value);
}
}
/**
* Determine if the holder is satisfied with configurations
* @return true if configuration optional or all pids supplied with configurations
*/
private boolean isSatisfied() {
if ( m_componentMetadata.isConfigurationOptional() || m_componentMetadata.isConfigurationIgnored() )
{
return true;
}
for ( int i = 0; i < m_componentMetadata.getConfigurationPid().size(); i++)
{
if ( m_configurations[i] != null)
{
continue;
}
if ( m_factoryPidIndex != null && m_factoryPidIndex == i)
{
continue;
}
return false;
}
return true;
}
@Override
public List<? extends ComponentManager<?>> getComponents()
{
synchronized ( m_components )
{
return getComponentManagers( );
}
}
@Override
public boolean isEnabled() {
return m_enabled;
}
private void wait(Promise<Void> promise)
{
boolean waited = false;
boolean interrupted = false;
while ( !waited )
{
try
{
promise.getValue();
waited = true;
}
catch ( InterruptedException e )
{
interrupted = true;
}
catch (InvocationTargetException e)
{
//this is not going to happen
}
}
if ( interrupted )
{
Thread.currentThread().interrupt();
}
}
@Override
public Promise<Void> enableComponents( final boolean async )
{
synchronized (enableLock)
{
if ( m_enablePromise != null)
{
return m_enablePromise;
}
wait( m_disablePromise );
List<AbstractComponentManager<S>> cms = new ArrayList<>();
synchronized ( m_components )
{
if ( isSatisfied() )
{
if ( m_factoryPidIndex == null ||
(m_componentMetadata.isObsoleteFactoryComponentFactory() && !m_componentMetadata.isConfigurationRequired()))
{
m_singleComponent = createComponentManager(false);
cms.add( m_singleComponent );
m_singleComponent.reconfigure(mergeProperties( null ), false, null);
}
if ( m_factoryPidIndex != null)
{
for (String pid: m_factoryConfigurations.keySet()) {
AbstractComponentManager<S> scm = createComponentManager(true);
m_components.put(pid, scm);
scm.reconfigure( mergeProperties( pid ), false, new TargetedPID(pid));
cms.add( scm );
}
}
}
m_enabled = true;
}
List<Promise<Void>> promises = new ArrayList<>();
for ( AbstractComponentManager<S> cm : cms )
{
promises.add(cm.enable( async ));
}
m_enablePromise = new Deferred<List<Void>>().resolveWith(Promises.<Void, Void>all(promises));
m_disablePromise = null;
return m_enablePromise;
}
}
@Override
public Promise<Void> disableComponents( final boolean async )
{
synchronized (enableLock)
{
if ( m_disablePromise != null)
{
return m_disablePromise;
}
wait( m_enablePromise );
List<AbstractComponentManager<S>> cms;
synchronized ( m_components )
{
m_enabled = false;
cms = getDirectComponentManagers( );
clearComponents();
}
List<Promise<Void>> promises = new ArrayList<>();
for ( AbstractComponentManager<S> cm : cms )
{
promises.add(cm.disable( async ));
}
m_disablePromise = new Deferred<List<Void>>().resolveWith(Promises.<Void, Void>all(promises));
m_enablePromise = null;
return m_disablePromise;
}
}
@Override
public void disposeComponents( final int reason )
{
List<AbstractComponentManager<S>> cms;
synchronized ( m_components )
{
cms = getDirectComponentManagers( );
clearComponents();
}
for ( AbstractComponentManager<S> cm : cms )
{
cm.dispose( reason );
}
}
@Override
public void disposed( SingleComponentManager<S> component )
{
// ensure the component is removed from the components map
synchronized ( m_components )
{
if ( !m_components.isEmpty() )
{
for ( Iterator<AbstractComponentManager<S>> vi = m_components.values().iterator(); vi.hasNext(); )
{
if ( component == vi.next() )
{
vi.remove();
break;
}
}
}
if ( component == m_singleComponent )
{
m_singleComponent = null;
}
}
}
/**
* Compares this {@code ImmediateComponentHolder} object to another object.
*
* <p>
* A ImmediateComponentHolder is considered to be <b>equal to </b> another
* ImmediateComponentHolder if the component names are equal(using
* {@code String.equals}) and they have the same bundle activator
*
* @param object The {@code ImmediateComponentHolder} object to be compared.
* @return {@code true} if {@code object} is a
* {@code ImmediateComponentHolder} and is equal to this object;
* {@code false} otherwise.
*/
@Override
public boolean equals(Object object)
{
if (!(object instanceof ConfigurableComponentHolder))
{
return false;
}
@SuppressWarnings("unchecked")
ConfigurableComponentHolder<S> other = (ConfigurableComponentHolder<S>) object;
return m_activator == other.m_activator
&& getName().equals(other.getName());
}
/**
* Returns a hash code value for the object.
*
* @return An integer which is a hash code value for this object.
*/
@Override
public int hashCode()
{
return getName().hashCode();
}
@Override
public String toString()
{
return "[ImmediateComponentHolder:" + getName() + "]";
}
String getName()
{
return m_componentMetadata.getName();
}
//---------- internal
/**
* Returns all component managers from the map and the single component manager, optionally also removing them
* from the map. If there are no component managers, <code>null</code>
* is returned. Must be called synchronized on m_components.
*/
List<AbstractComponentManager<S>> getComponentManagers( )
{
List<AbstractComponentManager<S>> cms = new ArrayList<>();
if ( m_singleComponent != null)
{
m_singleComponent.getComponentManagers(cms);
}
for (AbstractComponentManager<S> cm: m_components.values())
{
cm.getComponentManagers(cms);
}
return cms;
}
List<AbstractComponentManager<S>> getDirectComponentManagers( )
{
List<AbstractComponentManager<S>> cms = new ArrayList<>();
if ( m_singleComponent != null)
{
cms.add(m_singleComponent);
}
cms.addAll(m_components.values());
return cms;
}
void clearComponents()
{
m_components.clear();
m_singleComponent = null;
}
@Override
public ComponentLogger getLogger() {
return logger;
}
@Override
public TargetedPID getConfigurationTargetedPID(TargetedPID pid, TargetedPID factoryPid)
{
if ( factoryPid == null )
{
int index = m_componentMetadata.getPidIndex(pid);
if (index != -1)
{
return m_targetedPids[index];
}
return null;
}
//each factory configured component may have a different factory targeted pid.
synchronized (m_components)
{
return m_factoryTargetedPids.get(pid.getServicePid());
}
}
}