blob: 5a443796822dd9dd1e5d98c841e77d5a7a11244a [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.dm.impl;
import static org.apache.felix.dm.ComponentState.INACTIVE;
import static org.apache.felix.dm.ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED;
import static org.apache.felix.dm.ComponentState.STARTED;
import static org.apache.felix.dm.ComponentState.STARTING;
import static org.apache.felix.dm.ComponentState.STOPPED;
import static org.apache.felix.dm.ComponentState.STOPPING;
import static org.apache.felix.dm.ComponentState.TRACKING_OPTIONAL;
import static org.apache.felix.dm.ComponentState.WAITING_FOR_REQUIRED;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
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.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Stream;
import org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
import org.apache.felix.dm.ComponentDependencyDeclaration;
import org.apache.felix.dm.ComponentExecutorFactory;
import org.apache.felix.dm.ComponentState;
import org.apache.felix.dm.ComponentStateListener;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.DependencyManager;
import org.apache.felix.dm.Logger;
import org.apache.felix.dm.context.ComponentContext;
import org.apache.felix.dm.context.DependencyContext;
import org.apache.felix.dm.context.Event;
import org.apache.felix.dm.context.EventType;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.PrototypeServiceFactory;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.log.LogService;
/**
* Dependency Manager Component implementation.
*
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ComponentImpl implements Component, ComponentContext, ComponentDeclaration {
/**
* NullObject ServiceRegistration that is injected in components that don't provide any services.
*/
private static final ServiceRegistration<?> NULL_REGISTRATION = (ServiceRegistration<?>) Proxy
.newProxyInstance(ComponentImpl.class.getClassLoader(),
new Class[] { ServiceRegistration.class },
new DefaultNullObject());
/**
* Constant Used to get empty constructor by reflection.
*/
private static final CallbackTypeDef VOID = new CallbackTypeDef(new Class[][] {{}}, new Object[][] {{}});
/**
* Default Component Executor, which is by default single threaded. The first thread which schedules a task
* is the master thread and will execute all tasks that are scheduled by other threads at the time the master
* thread is executing. Internal tasks scheduled by the master thread are executed immediately (inline execution).
*
* If a ComponentExecutorFactory is provided in the OSGI registry, then this executor will be replaced by the
* executor returned by the ComponentExecutorFactory (however, the same semantic of the default executor is used:
* all tasks are serially executed).
*
* @see @link {@link ComponentExecutorFactory}
*/
private volatile Executor m_executor;
/**
* The current state of the component state machine.
*/
private ComponentState m_state = ComponentState.INACTIVE;
/**
* Indicates that the handleChange method is currently being executed.
*/
private boolean m_handlingChange;
/**
* List of dependencies. We use a COW list in order to avoid ConcurrentModificationException while iterating on the
* list and while a component synchronously add more dependencies from one of its callback method.
*/
private final CopyOnWriteArrayList<DependencyContext> m_dependencies = new CopyOnWriteArrayList<>();
/**
* List of Component state listeners. We use a COW list in order to avoid ConcurrentModificationException while iterating on the
* list and while a component synchronously adds more listeners from one of its callback method.
*/
private final List<ComponentStateListener> m_listeners = new CopyOnWriteArrayList<>();
/**
* Is the component active ?
*/
private boolean m_isStarted;
/**
* The Component logger.
*/
private final Logger m_logger;
/**
* The Component bundle context.
*/
private final BundleContext m_context;
/**
* The DependencyManager object that has created this component.
*/
private final DependencyManager m_manager;
/**
* The object used to create the component. Can be a class name, or the component implementation instance.
*/
private volatile Object m_componentDefinition;
/**
* The component instance.
*/
private Object m_componentInstance;
/**
* The service(s) provided by this component. Can be a String, or a String array.
*/
private volatile Object m_serviceName;
/**
* The service properties, if this component is providing a service.
*/
private volatile Dictionary<Object, Object> m_serviceProperties;
/**
* The component service registration. Can be a NullObject in case the component does not provide a service.
*/
private volatile ServiceRegistration<?> m_registration;
/**
* The component name as it must be returned by getName.
*/
private volatile String m_componentName;
/**
* Map of auto configured fields (BundleContext, ServiceRegistration, DependencyManager, or Component).
* By default, all fields mentioned above are auto configured (injected in class fields having the same type).
*/
private final Map<Class<?>, Boolean> m_autoConfig = new ConcurrentHashMap<>();
/**
* Map of auto configured instance fields that will be used when injected auto configured fields.
* @see #m_autoConfig
*/
private final Map<Class<?>, String> m_autoConfigInstance = new ConcurrentHashMap<>();
/**
* Data structure used to record the elapsed time used by component lifecycle callbacks.
* Key = callback name ("init", "start", "stop", "destroy").
* Value = elapsed time in nanos.
*/
private final Map<String, Long> m_stopwatch = new ConcurrentHashMap<>();
/**
* Unique component id.
*/
private final long m_id;
/**
* Unique ID generator.
*/
private final static AtomicLong m_idGenerator = new AtomicLong();
/**
* Holds all the services of a given dependency context. Caution: the last entry in the skiplist is the highest
* ranked service.
*/
private final Map<DependencyContext, ConcurrentSkipListSet<Event>> m_dependencyEvents = new HashMap<>();
/**
* Flag used to check if this component has been added in a DependencyManager object.
*/
private final AtomicBoolean m_active = new AtomicBoolean(false);
/**
* Init lifecycle callback. From that method, component are expected to add more extra dependencies.
* When this callback is invoked, all required dependencies have been injected.
*/
private volatile String m_callbackInit;
/**
* Start lifecycle callback. When this method is called, all required + all extra required dependencies defined in the
* init callback have been injected. The component may then perform its initialization.
*/
private volatile String m_callbackStart;
/**
* Stop callback. When this method is called, the component has been unregistered (if it provides any services),
* and all optional dependencies have been unbound.
*/
private volatile String m_callbackStop;
/**
* Destroy callback. When this method is called, all required dependencies defined in the init method have been unbound.
* After this method is called, then all required dependencies defined in the Activator will be unbound.
*/
private volatile String m_callbackDestroy;
/**
* By default, the init/start/stop/destroy callbacks are invoked on the component instance(s).
* But you can specify a separate callback instance.
*/
private volatile Object m_callbackInstance;
/**
* Component Factory instance object, that can be used to instantiate the component instance.
*/
private volatile Object m_instanceFactory;
/**
* Name of the Factory method to call.
*/
private volatile String m_instanceFactoryCreateMethod;
/**
* Composition Manager that can be used to create a graph of objects that are used to implement the component.
*/
private volatile Object m_compositionManager;
/**
* Name of the method used to invoke in order to get the list of component instance objects.
*/
private volatile String m_compositionManagerGetMethod;
/**
* The composition manager instance object, if specified.
*/
private volatile Object m_compositionManagerInstance;
/**
* The Component bundle.
*/
private final Bundle m_bundle;
/**
* Cache of callback invocation used to avoid calling the same callback twice.
* This situation may sometimes happen when the state machine triggers a lifecycle callback ("bind" call), and
* when the bind method registers a service which is tracked by another optional component dependency.
*
* @see org.apache.felix.dm.itest.api.FELIX4913_OptionalCallbackInvokedTwiceTest which reproduces the use case.
*/
private final Map<Event, Event> m_invokeCallbackCache = new IdentityHashMap<>();
/**
* Flag used to check if the start callback has been invoked.
* We use this flag to ensure that we only inject optional dependencies after the start callback has been called.
*/
private boolean m_startCalled;
/**
* Used to track the last state we delivered to any state listeners.
*/
private ComponentState m_lastStateDeliveredToListeners = ComponentState.INACTIVE;
/**
* Component service scope.
*/
private volatile ServiceScope m_scope = Component.ServiceScope.SINGLETON;
/**
* Indicates if injection of methods or class fields is disabled for this component.
*/
private volatile boolean m_injectionDisabled;
/**
* Prototype instance used when a component scope is not a singleton when when no init callback is defined.
*/
private final static Object PROTOTYPE_INSTANCE = new Object();
/**
* Default component declaration implementation.
*/
static class SCDImpl implements ComponentDependencyDeclaration {
private final String m_name;
private final int m_state;
private final String m_type;
public SCDImpl(String name, int state, String type) {
m_name = name;
m_state = state;
m_type = type;
}
public String getName() {
return m_name;
}
public String getSimpleName() {
return m_name;
}
public String getFilter() {
return null;
}
public int getState() {
return m_state;
}
public String getType() {
return m_type;
}
}
class FactoryBase {
final Map<Object, ComponentImpl> m_clones = new ConcurrentHashMap<>();
public Object getService(Bundle bundle, ServiceRegistration<Object> registration) {
// create a clone of the prototype for the consumer
ComponentImpl clone = (ComponentImpl) m_manager.createComponent()
.setImplementation(m_componentDefinition)
.setFactory(m_instanceFactory, m_instanceFactoryCreateMethod) // if not set, no effect
.setComposition(m_compositionManager, m_compositionManagerGetMethod) // if not set, no effect
.setCallbacks(m_callbackInstance, m_callbackInit, m_callbackStart, m_callbackStop, m_callbackDestroy); // if not set, no effect
clone.setThreadPool(m_executor);
clone.setAutoConfig(ServiceRegistration.class, false); // don't inject the ServiceRegistration for the moment
configureAutoConfigState(clone, ComponentImpl.this);
// copy prototype dependencies, except instance bound dependencies
getDependencies().stream().filter(dc -> ! dc.isInstanceBound()).map(dc -> dc.createCopy()).forEach(clone::add);
clone.setCallbacks(m_callbackInit, m_callbackStart, m_callbackStop, m_callbackDestroy);
m_listeners.forEach(clone::add);
int ctorIndex = clone.instantiateComponent(createScopedComponentConstructorArgs(bundle, registration));
// ctorIndex == -1 means we have used a factory to create the component
// if ctorIndex == 0 means we have used the first constructor (the one used to inject service registration and bundles
if (ctorIndex != 0) {
// we don't have injected the bundle and service registration in the constructor, so inject them in the class fields
autoConfigField(clone, ServiceRegistration.class, registration);
autoConfigField(clone, Bundle.class, bundle);
}
clone.start();
Object instance = clone.getInstance();
if (instance == null) {
// Race condition: the prototype is probably deactivating.
throw new IllegalStateException("Can't create prototype instance");
}
m_clones.put(instance, clone);
return instance;
}
public void ungetService(Bundle bundle, ServiceRegistration<Object> registration, Object service) {
ComponentImpl clone = m_clones.remove(service);
if (clone != null) {
clone.stop();
}
}
private void autoConfigField(ComponentImpl clone, Class<?> type, Object autoConfig) {
Boolean doAutoConfig = m_autoConfig.get(type);
clone.setAutoConfig(type, doAutoConfig == null ? true : doAutoConfig);
clone.autoConfigureImplementation(type, autoConfig);
}
}
class ComponentServiceFactory extends FactoryBase implements ServiceFactory<Object> {
}
class ComponentPrototypeServiceFactory extends FactoryBase implements PrototypeServiceFactory<Object> {
}
/**
* Constructor. Only used for tests.
*/
public ComponentImpl() {
this(null, null, new Logger(null));
}
/**
* Constructor
* @param context the component bundle context
* @param manager the manager used to create the component
* @param logger the logger to use
*/
public ComponentImpl(BundleContext context, DependencyManager manager, Logger logger) {
m_context = context;
m_bundle = context != null ? context.getBundle() : null;
m_manager = manager;
m_logger = logger;
m_executor = new SerialExecutor(m_logger);
m_autoConfig.put(BundleContext.class, Boolean.TRUE);
m_autoConfig.put(ServiceRegistration.class, Boolean.TRUE);
m_autoConfig.put(DependencyManager.class, Boolean.TRUE);
m_autoConfig.put(Component.class, Boolean.TRUE);
m_autoConfig.put(Bundle.class, Boolean.TRUE);
m_callbackInit = "init";
m_callbackStart = "start";
m_callbackStop = "stop";
m_callbackDestroy = "destroy";
m_id = m_idGenerator.getAndIncrement();
}
@Override
public <T> T createConfigurationType(Class<T> type, Dictionary<?, ?> config) {
Dictionary<String, Object> declaredServiceProperties = getDeclaredServiceProperties();
return Configurable.create(type, config, declaredServiceProperties);
}
@Override
public Executor getExecutor() {
return m_executor;
}
@Override
public ComponentImpl setDebug(String debugKey) {
// Force debug level in our logger
m_logger.setEnabledLevel(LogService.LOG_DEBUG);
m_logger.setDebugKey(debugKey);
return this;
}
@Override
public ComponentImpl add(final Dependency ... dependencies) {
getExecutor().execute(() -> {
List<DependencyContext> instanceBoundDeps = new ArrayList<>();
for (Dependency d : dependencies) {
DependencyContext dc = (DependencyContext) d;
if (dc.getComponentContext() != null) {
m_logger.err("%s can't be added to %s (dependency already added to component %s).", dc, ComponentImpl.this, dc.getComponentContext());
continue;
}
m_dependencyEvents.put(dc, new ConcurrentSkipListSet<Event>());
m_dependencies.add(dc);
generateNameBasedOnServiceAndProperties();
dc.setComponentContext(ComponentImpl.this);
if (!(m_state == ComponentState.INACTIVE)) {
dc.setInstanceBound(true);
instanceBoundDeps.add(dc);
}
}
startDependencies(instanceBoundDeps);
handleChange();
});
return this;
}
@Override
public ComponentImpl remove(final Dependency d) {
getExecutor().execute(() -> {
DependencyContext dc = (DependencyContext) d;
// First remove this dependency from the dependency list
m_dependencies.remove(d);
generateNameBasedOnServiceAndProperties();
// Now we can stop the dependency (our component won't be deactivated, it will only be unbound with
// the removed dependency).
if (!(m_state == ComponentState.INACTIVE)) {
dc.stop();
}
// Finally, cleanup the dependency events.
m_dependencyEvents.remove(d);
handleChange();
});
return this;
}
@Override
public void start() {
checkParamsConsistency();
if (m_active.compareAndSet(false, true)) {
getExecutor().execute(() -> {
m_isStarted = true;
handleChange();
});
}
}
@Override
public void stop() {
if (m_active.compareAndSet(true, false)) {
// try to be synchronous, even if a threadpool is used (best effort).
schedule(true /* bypass threadpool if possible */, () -> {
m_isStarted = false;
handleChange();
});
}
}
@SuppressWarnings("unchecked")
@Override
public ComponentImpl setInterface(String serviceName, Dictionary<?, ?> properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = (Dictionary<Object, Object>) properties;
generateNameBasedOnServiceAndProperties();
return this;
}
@SuppressWarnings("unchecked")
@Override
public ComponentImpl setInterface(String[] serviceName, Dictionary<?, ?> properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = (Dictionary<Object, Object>) properties;
generateNameBasedOnServiceAndProperties();
return this;
}
@Override
public ComponentImpl setInterface(Class<?> serviceName, Dictionary<?, ?> properties) {
return setInterface(serviceName.getName(), properties);
}
@Override
public ComponentImpl setInterface(Class<?>[] serviceName, Dictionary<?, ?> properties) {
String[] serviceNameStr = Stream.of(serviceName).map(clazz -> clazz.getName()).toArray(String[]::new);
return setInterface(serviceNameStr, properties);
}
@Override
public void handleEvent(final DependencyContext dc, final EventType type, final Event... event) {
// since this method can be invoked by anyone from any thread, we need to
// pass on the event to a runnable that we execute using the component's
// executor. There is one corner case: if this is a REMOVE event, and if we are using a threadpool,
// then make a best effort to try invoking the component unbind callback synchronously (to make
// sure the unbound service is not stopped at the time we call unbind on our component
// which depends on the removing service).
// This is just a best effort, and the removed event will be handled asynchronosly if our
// queue is currently being run by another thread, or by the threadpool.
boolean bypassThreadPoolIfPossible = (type == EventType.REMOVED);
schedule(bypassThreadPoolIfPossible, () -> {
try {
switch (type) {
case ADDED:
handleAdded(dc, event[0]);
break;
case CHANGED:
handleChanged(dc, event[0]);
break;
case REMOVED:
handleRemoved(dc, event[0]);
break;
case SWAPPED:
handleSwapped(dc, event[0], event[1]);
break;
}
} finally {
// Clear cache of component callbacks invocation, except if we are currently called from handleChange().
// (See FELIX-4913).
clearInvokeCallbackCache();
}
});
}
@Override
public Event getDependencyEvent(DependencyContext dc) {
ConcurrentSkipListSet<Event> events = m_dependencyEvents.get(dc);
return events.size() > 0 ? events.last() : null;
}
@Override
public Set<Event> getDependencyEvents(DependencyContext dc) {
return m_dependencyEvents.get(dc);
}
@Override
public ComponentImpl setAutoConfig(Class<?> clazz, boolean autoConfig) {
m_autoConfig.put(clazz, Boolean.valueOf(autoConfig));
return this;
}
@Override
public ComponentImpl setAutoConfig(Class<?> clazz, String instanceName) {
m_autoConfig.put(clazz, Boolean.valueOf(instanceName != null));
m_autoConfigInstance.put(clazz, instanceName);
return this;
}
@Override
public boolean getAutoConfig(Class<?> clazz) {
Boolean result = (Boolean) m_autoConfig.get(clazz);
return (result != null && result.booleanValue());
}
@Override
public String getAutoConfigInstance(Class<?> clazz) {
return (String) m_autoConfigInstance.get(clazz);
}
@SuppressWarnings("unchecked")
public <T> T getInstance() {
Object[] instances = getCompositionInstances();
return instances.length == 0 ? null : (T) instances[0];
}
public Object[] getInstances() {
return getCompositionInstances();
}
public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters) {
invokeCallbackMethod(instances, methodName, signatures, parameters, true);
}
public void invokeCallbackMethod(Object[] instances, String methodName, Class<?>[][] signatures, Object[][] parameters, boolean logIfNotFound) {
boolean callbackFound = false;
for (int i = 0; i < instances.length; i++) {
try {
InvocationUtil.invokeCallbackMethod(instances[i], methodName, signatures, parameters);
callbackFound |= true;
}
catch (NoSuchMethodException e) {
// if the method does not exist, ignore it
}
catch (InvocationTargetException e) {
// the method itself threw an exception, log that
m_logger.log(Logger.LOG_ERROR, "Invocation of '" + methodName + "' failed.", e.getCause());
callbackFound |= true;
}
catch (Throwable e) { // IllegalArgumentException (wrong params passed to the method), or IllegalAccessException (method can't be accessed)
m_logger.log(Logger.LOG_ERROR, "Could not invoke '" + methodName + "'.", e);
}
}
// If the callback is not found, we don't log if the method is on an AbstractDecorator.
// (Aspect or Adapter are not interested in user dependency callbacks)
if (logIfNotFound && ! callbackFound && ! (getInstance() instanceof AbstractDecorator)) {
m_logger.log(LogService.LOG_ERROR, "\"" + methodName + "\" callback not found on component instances " + Arrays.toString(instances));
}
}
public void invokeCallback(Object[] instances, String methodName, Class<?>[][] signatures, Supplier<?>[][] parameters, boolean logIfNotFound) {
boolean callbackFound = false;
for (int i = 0; i < instances.length; i++) {
try {
InvocationUtil.invokeCallbackMethod(instances[i], methodName, signatures, parameters);
callbackFound |= true;
}
catch (NoSuchMethodException e) {
// if the method does not exist, ignore it
}
catch (InvocationTargetException e) {
// the method itself threw an exception, log that
m_logger.log(Logger.LOG_ERROR, "Invocation of '" + methodName + "' failed.", e.getCause());
callbackFound |= true;
}
catch (Throwable e) {
m_logger.log(Logger.LOG_ERROR, "Could not invoke '" + methodName + "'.", e);
}
}
// If the callback is not found, we don't log if the method is on an AbstractDecorator.
// (Aspect or Adapter are not interested in user dependency callbacks)
if (logIfNotFound && ! callbackFound && ! (getInstance() instanceof AbstractDecorator)) {
m_logger.log(LogService.LOG_ERROR, "\"" + methodName + "\" callback not found on component instances " + Arrays.toString(instances));
}
}
@Override
public boolean isAvailable() {
return m_state == TRACKING_OPTIONAL;
}
@Override
public boolean isActive() {
return m_active.get();
}
@Override
public ComponentImpl add(ComponentStateListener listener) {
getExecutor().execute(() -> {
m_listeners.add(listener);
switch (m_lastStateDeliveredToListeners) {
case STARTING:
// this new listener missed the starting cb
listener.changed(ComponentImpl.this, STARTING);
break;
case TRACKING_OPTIONAL:
// this new listener missed the starting/started cb
listener.changed(ComponentImpl.this, STARTING);
listener.changed(ComponentImpl.this, TRACKING_OPTIONAL);
break;
case STOPPING:
// this new listener missed the starting/started/stopping cb
listener.changed(ComponentImpl.this, STARTING);
listener.changed(ComponentImpl.this, TRACKING_OPTIONAL);
listener.changed(ComponentImpl.this, STOPPING);
break;
case STOPPED:
// no need to call missed listener callbacks
break;
default:
break;
}
});
return this;
}
@Override
public ComponentImpl remove(ComponentStateListener listener) {
m_listeners.remove(listener);
return this;
}
@SuppressWarnings("unchecked")
@Override
public List<DependencyContext> getDependencies() {
return (List<DependencyContext>) m_dependencies.clone();
}
@Override
public ComponentImpl setImplementation(Object implementation) {
m_componentDefinition = implementation;
return this;
}
@SuppressWarnings("rawtypes")
@Override
public ServiceRegistration getServiceRegistration() {
return m_registration;
}
@SuppressWarnings("unchecked")
@Override
public <K,V> Dictionary<K, V> getServiceProperties() {
return (Dictionary<K, V>) calculateServiceProperties();
}
public <K,V> Dictionary<K, V> getDeclaredServiceProperties() {
return (m_serviceProperties != null) ? ServiceUtil.toR6Dictionary(m_serviceProperties) : ServiceUtil.toR6Dictionary(ServiceUtil.EMPTY_PROPERTIES);
// if (m_serviceProperties != null) {
// // Applied patch from FELIX-4304
// Hashtable<String, Object> serviceProperties = new Hashtable<>();
// addTo(serviceProperties, m_serviceProperties);
// return (Dictionary<K, V>) serviceProperties;
// }
// return (Dictionary<K, V>) ServiceUtil.EMPTY_PROPERTIES;
}
@Override
@SuppressWarnings("unchecked")
public ComponentImpl setServiceProperties(final Dictionary<?, ?> serviceProperties) {
getExecutor().execute(() -> {
Dictionary<String, Object> properties = null;
m_serviceProperties = (Dictionary<Object, Object>) serviceProperties;
generateNameBasedOnServiceAndProperties();
if ((m_registration != null) && (m_serviceName != null)) {
properties = calculateServiceProperties();
m_registration.setProperties(properties);
}
});
return this;
}
public ComponentImpl setCallbacks(String init, String start, String stop, String destroy) {
ensureNotActive();
m_callbackInit = init;
m_callbackStart = start;
m_callbackStop = stop;
m_callbackDestroy = destroy;
return this;
}
public ComponentImpl setCallbacks(Object instance, String init, String start, String stop, String destroy) {
ensureNotActive();
m_callbackInstance = instance;
m_callbackInit = init;
m_callbackStart = start;
m_callbackStop = stop;
m_callbackDestroy = destroy;
return this;
}
@Override
public ComponentImpl setFactory(Object factory, String createMethod) {
ensureNotActive();
m_instanceFactory = factory;
m_instanceFactoryCreateMethod = createMethod;
return this;
}
@Override
public ComponentImpl setFactory(String createMethod) {
return setFactory(null, createMethod);
}
@Override
public ComponentImpl setComposition(Object instance, String getMethod) {
ensureNotActive();
m_compositionManager = instance;
m_compositionManagerGetMethod = getMethod;
return this;
}
@Override
public ComponentImpl setComposition(String getMethod) {
return setComposition(null, getMethod);
}
@Override
public DependencyManager getDependencyManager() {
return m_manager;
}
@Override
public boolean injectionDisabled() {
return m_injectionDisabled;
}
public ComponentDependencyDeclaration[] getComponentDependencies() {
List<DependencyContext> deps = getDependencies();
if (deps != null) {
ComponentDependencyDeclaration[] result = new ComponentDependencyDeclaration[deps.size()];
for (int i = 0; i < result.length; i++) {
DependencyContext dep = (DependencyContext) deps.get(i);
if (dep instanceof ComponentDependencyDeclaration) {
result[i] = (ComponentDependencyDeclaration) dep;
}
else {
result[i] = new SCDImpl(dep.toString(), (dep.isAvailable() ? 1 : 0) + (dep.isRequired() ? 2 : 0), dep.getClass().getName());
}
}
return result;
}
return null;
}
public String getName() {
StringBuilder sb = new StringBuilder();
Object serviceName = m_serviceName;
if( (!(serviceName instanceof String[])) && (!(serviceName instanceof String))) {
// The component does not provide a service, use the component definition as the name.
Object componentDefinition = m_componentDefinition;
if (componentDefinition != null) {
sb.append(toString(componentDefinition));
} else {
// No component definition means we are using a factory. If the component instance is available use it as the component name,
// alse use teh factory object as the component name.
Object componentInstance = m_componentInstance;
if (componentInstance != null) {
sb.append(componentInstance.getClass().getName());
} else {
// Check if a factory is set.
Object instanceFactory = m_instanceFactory;
if (instanceFactory != null) {
sb.append(toString(instanceFactory));
} else {
sb.append(super.toString());
}
}
}
m_componentName = sb.toString();
}
return m_componentName;
}
@Override
public ComponentImpl setScope(ServiceScope scope) {
m_scope = scope;
return this;
}
ServiceScope getScope() {
return m_scope;
}
private void checkParamsConsistency() {
switch (m_scope) {
case PROTOTYPE:
case BUNDLE:
if (m_serviceName == null) {
throw new IllegalStateException("No service interface specified for scoped service");
}
if ((!(m_componentDefinition instanceof Class)) && m_instanceFactoryCreateMethod == null) {
throw new IllegalStateException(
"The component instance must be created using either a class or a factory object when scope is not singleton.");
}
}
}
private void generateNameBasedOnServiceAndProperties() {
StringBuilder sb = new StringBuilder();
Object serviceName = m_serviceName;
// If the component provides service(s), return the services as the component name.
if (serviceName instanceof String[]) {
String[] names = (String[]) serviceName;
for (int i = 0; i < names.length; i++) {
if (i > 0) {
sb.append(", ");
}
sb.append(names[i]);
}
ServiceUtil.appendProperties(sb, calculateServiceProperties());
} else if(serviceName instanceof String) {
sb.append(serviceName.toString());
ServiceUtil.appendProperties(sb, calculateServiceProperties());
}
m_componentName = sb.toString();
}
@Override
public BundleContext getBundleContext() {
return m_context;
}
@Override
public Bundle getBundle() {
return m_bundle;
}
public long getId() {
return m_id;
}
public String getClassName() {
Object serviceInstance = m_componentInstance;
if (serviceInstance != null) {
return serviceInstance.getClass().getName();
}
Object implementation = m_componentDefinition;
if (implementation != null) {
if (implementation instanceof Class) {
return ((Class<?>) implementation).getName();
}
return implementation.getClass().getName();
}
Object instanceFactory = m_instanceFactory;
if (instanceFactory != null) {
return toString(instanceFactory);
} else {
// unexpected.
return ComponentImpl.class.getName();
}
}
public String[] getServices() {
if (m_serviceName instanceof String[]) {
return (String[]) m_serviceName;
} else if (m_serviceName instanceof String) {
return new String[] { (String) m_serviceName };
} else {
return null;
}
}
public int getState() {
return (isAvailable() ? ComponentDeclaration.STATE_REGISTERED : ComponentDeclaration.STATE_UNREGISTERED);
}
public void ensureNotActive() {
if (m_active.get()) {
throw new IllegalStateException("Can't modify an already started component.");
}
}
public ComponentDeclaration getComponentDeclaration() {
return this;
}
@Override
public String toString() {
if (m_logger.getDebugKey() != null) {
return m_logger.getDebugKey();
}
return getClassName();
}
@Override
public void setThreadPool(Executor threadPool) {
ensureNotActive();
m_executor = new DispatchExecutor(threadPool, m_logger);
}
@Override
public Logger getLogger() {
return m_logger;
}
@Override
public Map<String, Long> getCallbacksTime() {
return m_stopwatch;
}
@Override
public ComponentContext instantiateComponent() {
CallbackTypeDef ctorArgs = null;
switch (m_scope) {
case SINGLETON:
// When a component scope is singleton, we'll use the following constructor or factory "create" method signatures:
ctorArgs = VOID;
break;
case PROTOTYPE:
case BUNDLE:
if (m_injectionDisabled) {
m_componentInstance = PROTOTYPE_INSTANCE;
return this;
} else {
ctorArgs = createScopedComponentConstructorArgs(null, null);
}
break;
default:
throw new IllegalStateException("Invalid scope: " + m_scope);
}
// Create the factory "create" method signatures types
instantiateComponent(ctorArgs);
return this;
}
// ---------------------- Package/Private methods ---------------------------
/**
* Creates a constructor argument descritor for a scoped component (having scope=BUNDLE|PROTOTYPE)
*/
private CallbackTypeDef createScopedComponentConstructorArgs(Bundle b, ServiceRegistration sr) {
return new CallbackTypeDef(
new Class[][] {{Bundle.class, ServiceRegistration.class}, {Bundle.class, ServiceRegistration.class, BundleContext.class}, {}},
new Object[][] {{b,sr}, {b,sr, m_context}, {}});
}
/**
* Creates the component instance.
* @param ctorArgs the constructor argument signatures
* @return the index of the constructor argument signatures that was used when creating the component. -1 means the component has been created using a factory "create" method.
*/
private int instantiateComponent(CallbackTypeDef ctorArgs) {
m_logger.debug("instantiating component.");
int ctorArgsUsed = -1;
if (m_componentInstance == null) {
if (m_componentDefinition instanceof Class) {
try {
InvocationUtil.ComponentInstance ci = InvocationUtil.createInstance((Class<?>) m_componentDefinition, ctorArgs);
m_componentInstance = ci.m_instance;
ctorArgsUsed = ci.m_ctorIndex;
} catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Could not instantiate class " + m_componentDefinition, e);
}
} else {
if (m_instanceFactoryCreateMethod != null) {
Object factory = null;
if (m_instanceFactory != null) {
if (m_instanceFactory instanceof Class) {
try {
InvocationUtil.ComponentInstance ci = InvocationUtil.createInstance((Class<?>) m_instanceFactory, VOID);
factory = ci.m_instance;
} catch (Exception e) {
m_logger.log(Logger.LOG_ERROR,
"Could not create factory instance of class " + m_instanceFactory + ".", e);
}
} else {
factory = m_instanceFactory;
}
} else {
// TODO review if we want to try to default to something if not specified
// for now the JavaDoc of setFactory(method) reflects the fact that we need
// to review it
}
if (factory == null) {
m_logger.log(Logger.LOG_ERROR, "Factory cannot be null.");
} else {
try {
CallbackTypeDef factoryArgs = new CallbackTypeDef(
new Class[][] {{}, {Component.class}},
new Object[][] {{}, {this}});
m_componentInstance = InvocationUtil.invokeMethod(factory, factory.getClass(),
m_instanceFactoryCreateMethod, factoryArgs.m_sigs, factoryArgs.m_args, false);
} catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Could not create service instance using factory " + factory
+ " method " + m_instanceFactoryCreateMethod + ".", e);
}
}
}
}
if (m_componentInstance == null) {
m_componentInstance = m_componentDefinition;
}
// configure the bundle context
autoConfigureImplementation(BundleContext.class, m_context);
autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
autoConfigureImplementation(DependencyManager.class, m_manager);
autoConfigureImplementation(Component.class, this);
}
return ctorArgsUsed;
}
private String toString(Object implementation) {
if (implementation instanceof Class) {
return (((Class<?>) implementation).getName());
} else {
// If the implementation instance does not override "toString", just display
// the class name, else display the component using its toString method
try {
Method m = implementation.getClass().getMethod("toString", new Class[0]);
if (m.getDeclaringClass().equals(Object.class)) {
return implementation.getClass().getName();
} else {
return implementation.toString();
}
} catch (java.lang.NoSuchMethodException e) {
// Just display the class name
return implementation.getClass().getName();
}
}
}
/**
* Runs the state machine, to see if a change event has to trigger some component state transition.
*/
private void handleChange() {
if (isHandlingChange()) {
return;
}
m_logger.debug("handleChanged");
handlingChange(true);
try {
ComponentState oldState;
ComponentState newState;
do {
oldState = m_state;
newState = calculateNewState(oldState);
m_logger.debug("%s -> %s", oldState, newState);
m_state = newState;
} while (performTransition(oldState, newState));
} finally {
handlingChange(false);
clearInvokeCallbackCache();
m_logger.debug("end handling change.");
}
}
/**
* Based on the current state, calculate the new state.
*/
private ComponentState calculateNewState(ComponentState currentState) {
if (currentState == INACTIVE) {
if (m_isStarted) {
return WAITING_FOR_REQUIRED;
}
}
if (currentState == WAITING_FOR_REQUIRED) {
if (!m_isStarted) {
return INACTIVE;
}
if (allRequiredAvailable()) {
return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
}
}
if (currentState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
if (m_isStarted && allRequiredAvailable()) {
if (allInstanceBoundAvailable()) {
return TRACKING_OPTIONAL;
}
return currentState;
}
return WAITING_FOR_REQUIRED;
}
if (currentState == TRACKING_OPTIONAL) {
if (m_isStarted && allRequiredAvailable() && allInstanceBoundAvailable()) {
return currentState;
}
return INSTANTIATED_AND_WAITING_FOR_REQUIRED;
}
return currentState;
}
/**
* Perform all the actions associated with state transitions.
* @returns true if a transition was performed.
**/
private boolean performTransition(ComponentState oldState, ComponentState newState) {
if (oldState == ComponentState.INACTIVE && newState == ComponentState.WAITING_FOR_REQUIRED) {
initPrototype();
startDependencies(m_dependencies);
notifyListeners(newState);
return true;
}
if (oldState == WAITING_FOR_REQUIRED && newState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
instantiateComponent();
invokeAutoConfigDependencies();
invokeAddRequiredDependencies();
invoke(m_callbackInit);
notifyListeners(newState);
return true;
}
if (oldState == INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == TRACKING_OPTIONAL) {
invokeAutoConfigInstanceBoundDependencies();
invokeAddRequiredInstanceBoundDependencies();
notifyListeners(STARTING);
invokeStart();
notifyListeners(STARTED);
invokeAddOptionalDependencies();
registerService();
notifyListeners(newState);
return true;
}
if (oldState == TRACKING_OPTIONAL && newState == INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
notifyListeners(STOPPING);
unregisterService();
invokeRemoveOptionalDependencies();
invokeStop();
invokeRemoveRequiredInstanceBoundDependencies();
notifyListeners(newState);
notifyListeners(STOPPED);
return true;
}
if (oldState == INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == WAITING_FOR_REQUIRED) {
invoke(m_callbackDestroy);
removeInstanceBoundDependencies();
invokeRemoveRequiredDependencies();
notifyListeners(newState);
if (! someDependenciesNeedInstance()) {
destroyComponent();
}
return true;
}
if (oldState == WAITING_FOR_REQUIRED && newState == INACTIVE) {
stopDependencies();
destroyComponent();
notifyListeners(newState);
cleanup();
return true;
}
return false;
}
private void cleanup() {
m_dependencyEvents.values().forEach(eventList -> {
eventList.forEach(event -> event.close());
eventList.clear();
});
}
private void invokeStart() {
// Only invoke start callback for singleton components.
// We don't invoke start callbacks for components with scope=prototype or bundle because
// prototypes (bundle/prototype) components are only used to register ServiceFactory (or PrototypeServiceFactory) services.
if (m_scope == ServiceScope.SINGLETON) {
invoke(m_callbackStart);
m_startCalled = true;
}
}
private void invokeStop() {
// Only invoke stop callback for singleton components.
// We don't invoke stop callbacks for components with scope=prototype or bundle because
// prototypes (bundle/prototype) components are only used to register ServiceFactory (or PrototypeServiceFactory) services.
if (m_scope == ServiceScope.SINGLETON) {
invoke(m_callbackStop);
m_startCalled = false;
}
}
/**
* Sets the m_handlingChange flag that indicates if the state machine is currently running the handleChange method.
*/
private void handlingChange(boolean transiting) {
m_handlingChange = transiting;
}
/**
* Are we currently running the handleChange method ?
*/
private boolean isHandlingChange() {
return m_handlingChange;
}
/**
* Then handleEvent calls this method when a dependency service is being added.
*/
private void handleAdded(DependencyContext dc, Event e) {
if (! m_isStarted) {
return;
}
m_logger.debug("handleAdded %s", e);
Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
dependencyEvents.add(e);
dc.setAvailable(true);
// In the following switch block, we sometimes only recalculate state changes
// if the dependency is fully started. If the dependency is not started,
// it means it is actually starting (the service tracker is executing the open method).
// And in this case, depending on the state, we don't recalculate state changes now.
//
// All this is done for two reasons:
// 1- optimization: it is preferable to recalculate state changes once we know about all currently
// available dependency services (after the tracker has returned from its open method).
// 2- This also allows to determine the list of currently available dependency services before calling
// the component start() callback.
switch (m_state) {
case WAITING_FOR_REQUIRED:
if (dc.isStarted() && dc.isRequired()) {
handleChange();
}
break;
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
if (!dc.isInstanceBound()) {
invokeCallback(dc, EventType.ADDED, e);
updateInstance(dc, e, false, true);
} else {
if (dc.isStarted() && dc.isRequired()) {
handleChange();
}
}
break;
case TRACKING_OPTIONAL:
invokeCallback(dc, EventType.ADDED, e);
updateInstance(dc, e, false, true);
break;
default:
}
}
/**
* Then handleEvent calls this method when a dependency service is being changed.
*/
private void handleChanged(final DependencyContext dc, final Event e) {
if (! m_isStarted) {
return;
}
Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
dependencyEvents.remove(e);
dependencyEvents.add(e);
switch (m_state) {
case TRACKING_OPTIONAL:
invokeCallback(dc, EventType.CHANGED, e);
updateInstance(dc, e, true, false);
break;
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
if (!dc.isInstanceBound()) {
invokeCallback(dc, EventType.CHANGED, e);
updateInstance(dc, e, true, false);
}
break;
default:
// noop
}
}
/**
* Then handleEvent calls this method when a dependency service is being removed.
*/
private void handleRemoved(DependencyContext dc, Event e) {
try {
if (! m_isStarted) {
return;
}
// Check if the dependency is still available.
Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
int size = dependencyEvents.size();
if (dependencyEvents.contains(e)) {
size--; // the dependency is currently registered and is about to be removed.
}
dc.setAvailable(size > 0);
// If the dependency is now unavailable, we have to recalculate state change. This will result in invoking the
// "removed" callback with the removed dependency (which we have not yet removed from our dependency events list.).
// But we don't recalculate the state if the dependency is not started (if not started, it means that it is currently starting,
// and the tracker is detecting a removed service).
if (size == 0 && dc.isStarted()) {
handleChange();
}
// Now, really remove the dependency event.
dependencyEvents.remove(e);
// Depending on the state, we possible have to invoke the callbacks and update the component instance.
switch (m_state) {
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
if (!dc.isInstanceBound()) {
invokeCallback(dc, EventType.REMOVED, e);
updateInstance(dc, e, false, false);
}
break;
case TRACKING_OPTIONAL:
invokeCallback(dc, EventType.REMOVED, e);
updateInstance(dc, e, false, false);
break;
default:
}
} finally {
e.close();
}
}
private void handleSwapped(DependencyContext dc, Event oldEvent, Event newEvent) {
try {
if (! m_isStarted) {
return;
}
Set<Event> dependencyEvents = m_dependencyEvents.get(dc);
dependencyEvents.remove(oldEvent);
dependencyEvents.add(newEvent);
// Depending on the state, we possible have to invoke the callbacks and update the component instance.
switch (m_state) {
case WAITING_FOR_REQUIRED:
// No need to swap, we don't have yet injected anything
break;
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
// Only swap *non* instance-bound dependencies
if (!dc.isInstanceBound()) {
invokeSwapCallback(dc, oldEvent, newEvent);
}
break;
case TRACKING_OPTIONAL:
invokeSwapCallback(dc, oldEvent, newEvent);
break;
default:
}
} finally {
oldEvent.close();
}
}
private boolean allRequiredAvailable() {
boolean available = true;
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && !d.isInstanceBound()) {
if (!d.isAvailable()) {
available = false;
break;
}
}
}
return available;
}
private boolean allInstanceBoundAvailable() {
boolean available = true;
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && d.isInstanceBound()) {
if (!d.isAvailable()) {
available = false;
break;
}
}
}
return available;
}
private boolean someDependenciesNeedInstance() {
for (DependencyContext d : m_dependencies) {
if (d.needsInstance()) {
return true;
}
}
return false;
}
/**
* Updates the component instance(s).
* @param dc the dependency context for the updating dependency service
* @param event the event holding the updating service (service + properties)
* @param update true if dependency service properties are updating, false if not. If false, it means
* that a dependency service is being added or removed. (see the "add" flag).
* @param add true if the dependency service has been added, false if it has been removed. This flag is
* ignored if the "update" flag is true (because the dependency properties are just being updated).
*/
private void updateInstance(DependencyContext dc, Event event, boolean update, boolean add) {
if (dc.isAutoConfig()) {
updateImplementation(dc.getAutoConfigType(), dc, dc.getAutoConfigName(), event, update, add);
}
if (dc.isPropagated() && m_registration != null) {
m_registration.setProperties(calculateServiceProperties());
}
}
private void startDependencies(List<DependencyContext> dependencies) {
// Start first optional dependencies first.
m_logger.debug("startDependencies.");
List<DependencyContext> requiredDeps = new ArrayList<>();
for (DependencyContext d : dependencies) {
if (d.isRequired()) {
requiredDeps.add(d);
continue;
}
if (d.needsInstance()) {
instantiateComponent();
}
d.start();
}
// now, start required dependencies.
for (DependencyContext d : requiredDeps) {
if (d.needsInstance()) {
instantiateComponent();
}
d.start();
}
}
private void stopDependencies() {
for (DependencyContext d : m_dependencies) {
d.stop();
}
}
private void registerService() {
if (m_context != null && m_serviceName != null) {
ServiceRegistrationImpl wrapper = new ServiceRegistrationImpl();
m_registration = wrapper;
autoConfigureImplementation(ServiceRegistration.class, m_registration);
// service name can either be a string or an array of strings
ServiceRegistration<?> registration;
// determine service properties
Dictionary<String, Object> properties = calculateServiceProperties();
// register the service
try {
Object componentInstance = null;
switch (m_scope) {
case SINGLETON: componentInstance = m_componentInstance; break;
case BUNDLE: componentInstance = new ComponentServiceFactory(); break;
case PROTOTYPE: componentInstance = new ComponentPrototypeServiceFactory(); break;
}
if (m_serviceName instanceof String) {
registration = m_context.registerService((String) m_serviceName, componentInstance, properties);
}
else {
registration = m_context.registerService((String[]) m_serviceName, componentInstance, properties);
}
wrapper.setServiceRegistration(registration);
}
catch (IllegalArgumentException iae) {
m_logger.log(Logger.LOG_ERROR, "Could not register service " + m_componentInstance, iae);
// set the registration to an illegal state object, which will make all invocations on this
// wrapper fail with an ISE (which also occurs when the SR becomes invalid)
wrapper.setIllegalState();
}
}
}
private void unregisterService() {
if (m_serviceName != null && m_registration != null) {
try {
if (m_bundle != null && (m_bundle.getState() == Bundle.STARTING || m_bundle.getState() == Bundle.ACTIVE || m_bundle.getState() == Bundle.STOPPING)) {
m_registration.unregister();
}
} catch (IllegalStateException e) { /* Should we really log this ? */}
autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
m_registration = null;
}
}
private Dictionary<String, Object> calculateServiceProperties() {
Dictionary<String, Object> properties = new Hashtable<>();
// First add propagated dependency service properties which don't override our component service properties
Predicate<DependencyContext> dependencyPropagated = (dc) -> dc.isPropagated() && dc.isAvailable();
m_dependencies.stream()
.filter(dc -> dependencyPropagated.test(dc) && ! dc.overrideServiceProperties())
.forEach(dc -> addTo(properties, dc.getProperties()));
// Now add our component service properties, which override previously added propagated dependency service properties
addTo(properties, m_serviceProperties);
// Finally, add dependency service properties which override our component service properties
m_dependencies.stream()
.filter(dc -> dependencyPropagated.test(dc) && dc.overrideServiceProperties())
.forEach(dc -> addTo(properties, dc.getProperties()));
return properties; // FELIX-5683: now we never return null
}
private void addTo(Dictionary<String, Object> properties, Dictionary<?, Object> additional) {
if (properties == null) {
throw new IllegalArgumentException("Dictionary to add to cannot be null.");
}
if (additional != null) {
Enumeration<?> e = additional.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
properties.put(key.toString(), additional.get(key));
}
}
}
private void destroyComponent() {
m_componentInstance = null;
}
private void invokeAddRequiredDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && !d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.ADDED, e);
}
}
}
}
private void invokeAutoConfigDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isAutoConfig() && !d.isInstanceBound()) {
configureImplementation(d.getAutoConfigType(), d, d.getAutoConfigName());
}
}
}
private void invokeAutoConfigInstanceBoundDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isAutoConfig() && d.isInstanceBound()) {
configureImplementation(d.getAutoConfigType(), d, d.getAutoConfigName());
}
}
}
private void invokeAddRequiredInstanceBoundDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isRequired() && d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.ADDED, e);
}
}
}
}
private void invokeAddOptionalDependencies() {
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.ADDED, e);
}
}
}
}
private void invokeRemoveRequiredDependencies() {
for (DependencyContext d : m_dependencies) {
if (!d.isInstanceBound() && d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.REMOVED, e);
}
}
}
}
private void invokeRemoveOptionalDependencies() {
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.REMOVED, e);
}
}
}
}
private void invokeRemoveRequiredInstanceBoundDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isInstanceBound() && d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
invokeCallback(d, EventType.REMOVED, e);
}
}
}
}
/**
* This method ensures that a dependency callback is invoked only one time;
* It also ensures that if the dependency callback is optional, then we only
* invoke the bind method if the component start callback has already been called.
*/
private void invokeCallback(DependencyContext dc, EventType type, Event event) {
// don't inject anything if the component instance is a hidden prototype instance
if (m_injectionDisabled) {
return;
}
if (! dc.isRequired() && ! m_startCalled) {
return;
}
// First, check if the callback has not already been called (see FELIX-4913)
if (m_invokeCallbackCache.put(event, event) == null) {
// FELIX-5155: we must not invoke callbacks on our special internal components (adapters/aspects) if the dependency is not the first one, or
// if the internal component is a Factory Pid Adapter.
// For aspects/adapters, the first dependency only need to be injected, not the other extra dependencies added by user.
// (in fact, we do this because extra dependencies (added by user) may contain a callback instance, and we really don't want to invoke the callbacks twice !
Object mainComponentImpl = getInstance();
if (mainComponentImpl instanceof AbstractDecorator) {
if (mainComponentImpl instanceof FactoryConfigurationAdapterImpl.AdapterImpl || dc != m_dependencies.get(0)) {
return;
}
}
dc.invokeCallback(type, event);
}
}
/**
* Invokes a swap callback, except if the dependency is optional and the component is
* not started (optional dependencies are always injected while the component is started).
*/
private void invokeSwapCallback(DependencyContext dc, Event oldEvent, Event newEvent) {
if (! dc.isRequired() && ! m_startCalled) {
return;
}
dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
}
/**
* Removes and closes all instance bound dependencies.
* This method is called when a component is destroyed.
*/
private void removeInstanceBoundDependencies() {
for (DependencyContext dep : m_dependencies) {
if (dep.isInstanceBound()) {
m_dependencies.remove(dep);
generateNameBasedOnServiceAndProperties();
dep.stop();
}
}
}
/**
* Clears the cache of invoked components callbacks.
* We only clear the cache when the state machine is not running.
* The cache is used to avoid calling the same bind callback twice.
* See FELIX-4913.
*/
private void clearInvokeCallbackCache() {
if (! isHandlingChange()) {
m_invokeCallbackCache.clear();
}
}
private void invoke(String name) {
if (name != null) {
// if a callback instance was specified, look for the method there, if not,
// ask the service for its composition instances
Object[] instances = m_callbackInstance != null ? new Object[] { m_callbackInstance } : getCompositionInstances();
long t1 = System.nanoTime();
try {
invokeCallbackMethod(instances, name,
new Class[][] {{ Component.class }, {}},
new Object[][] {{ this }, {}},
false);
} finally {
long t2 = System.nanoTime();
m_stopwatch.put(name, t2 - t1);
}
}
}
private void notifyListeners(ComponentState state) {
if (m_scope == ServiceScope.SINGLETON) {
m_lastStateDeliveredToListeners = state;
for (ComponentStateListener l : m_listeners) {
try {
l.changed(this, state);
} catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Exception caught while invoking component state listener", e);
}
}
}
}
void autoConfigureImplementation(Class<?> clazz, Object instance) {
if (((Boolean) m_autoConfig.get(clazz)).booleanValue()) {
configureImplementation(clazz, instance, (String) m_autoConfigInstance.get(clazz));
}
}
/**
* Configure a field in the service implementation. The service implementation
* is searched for fields that have the same type as the class that was specified
* and for each of these fields, the specified instance is filled in.
*
* @param clazz the class to search for
* @param instance the object to fill in the implementation class(es) field
* @param instanceName the name of the instance to fill in, or <code>null</code> if not used
*/
void configureImplementation(Class<?> clazz, Object instance, String fieldName) {
// don't inject anything if the component instance is a hidden prototype instance
if (m_injectionDisabled) {
return;
}
Object[] targets = getInstances();
if (! FieldUtil.injectField(targets, fieldName, clazz, instance, m_logger) && fieldName != null) {
// If the target is an abstract decorator (i.e: an adapter, or an aspect), we must not log warnings
// if field has not been injected.
if (! (getInstance() instanceof AbstractDecorator)) {
m_logger.log(Logger.LOG_ERROR, "Could not inject " + instance + " to field \"" + fieldName
+ "\" at any of the following component instances: " + Arrays.toString(targets));
}
}
}
private void configureImplementation(Class<?> clazz, DependencyContext dc, String fieldName) {
// don't inject anything if the component instance is a hidden prototype instance
if (m_injectionDisabled) {
return;
}
Object[] targets = getInstances();
if (! FieldUtil.injectDependencyField(targets, fieldName, clazz, dc, m_logger) && fieldName != null) {
// If the target is an abstract decorator (i.e: an adapter, or an aspect), we must not log warnings
// if field has not been injected.
if (! (getInstance() instanceof AbstractDecorator)) {
m_logger.log(Logger.LOG_ERROR, "Could not inject dependency " + clazz.getName() + " to field \""
+ fieldName + "\" at any of the following component instances: " + Arrays.toString(targets));
}
}
}
/**
* Update the component instances.
*
* @param clazz the class of the dependency service to inject in the component instances
* @param dc the dependency context for the updating dependency service
* @param fieldName the component instances fieldname to fill in with the updated dependency service
* @param event the event holding the updating service (service + properties)
* @param update true if dependency service properties are updating, false if not. If false, it means
* that a dependency service is being added or removed. (see the "add" flag).
* @param add true if the dependency service has been added, false if it has been removed. This flag is
* ignored if the "update" flag is true (because the dependency properties are just being updated).
*/
private void updateImplementation(Class<?> clazz, DependencyContext dc, String fieldName, Event event, boolean update, boolean add) {
if (! m_injectionDisabled) {
Object[] targets = getInstances();
FieldUtil.updateDependencyField(targets, fieldName, update, add, clazz, event, dc, m_logger);
}
}
private Object[] getCompositionInstances() {
Object[] instances = null;
if (m_compositionManagerGetMethod != null) {
if (m_compositionManager != null) {
m_compositionManagerInstance = m_compositionManager;
}
else {
m_compositionManagerInstance = m_componentInstance;
}
if (m_compositionManagerInstance != null) {
try {
instances = (Object[]) InvocationUtil.invokeMethod(m_compositionManagerInstance, m_compositionManagerInstance.getClass(), m_compositionManagerGetMethod, new Class[][] {{}}, new Object[][] {{}}, false);
}
catch (Exception e) {
m_logger.log(Logger.LOG_ERROR, "Could not obtain instances from the composition manager.", e);
instances = m_componentInstance == null ? new Object[] {} : new Object[] { m_componentInstance };
}
} else {
return new Object[0];
}
}
else {
instances = m_componentInstance == null ? new Object[] {} : new Object[] { m_componentInstance };
}
return instances;
}
/**
* Executes a task using our component queue. The queue associated to this component is by default a "SerialExecutor", or
* a "DispatchExecutor" if a threadpool is used (that is: if a ComponentExecutorFactory has returned an executor this our
* component).
*
* A SerialExecutor always try to execute the task synchronously, except if another master thread is currently
* running the SerialExecutor queue.
* A DispatchExecutor always schedule the task asynchronously in a threadpool, but can optionally try to execute the task synchronously
* (the threadpool is bypassed only if the queue is not currently being run by the threadpool).
*
* The "bypassThreadPoolIfPossible" argument is only used in case there is a threadpool configured. If no threadpool is used,
* this argument is ignored and the task is run synchronously if the serial queue is not concurrently run from another
* master threads.
*
* @param bypassThreadPoolIfPossible This argument is only used if a threadpool is configured for this component. if true,
* it means that if a threadpool is configured, then we attempt to run the task synchronously and not using the threadpool,
* but if the queue is currently run from the threadpool, then the task is scheduled in it.
* false means we always use the threadpool and the task is executed asynchronously.
* @param task the task to execute.
*/
private void schedule(boolean bypassThreadPoolIfPossible, Runnable task) {
Executor exec = getExecutor();
if (exec instanceof DispatchExecutor) {
// We are using a threadpool. If byPassThreadPoolIsPossible is true, it means we can try to run the task synchronously from the current
// thread. But if our queue is currently run from the threadpool, then the task will be run from it (asynchronously).
((DispatchExecutor) exec).execute(task, ! bypassThreadPoolIfPossible);
} else {
// The queue is a serial queue: the task will be run synchronously, except if another master thread is currently running the queue.
exec.execute(task);
}
}
private void configureAutoConfigState(Component target, ComponentContext source) {
configureAutoConfigState(target, source, BundleContext.class);
configureAutoConfigState(target, source, ServiceRegistration.class);
configureAutoConfigState(target, source, DependencyManager.class);
configureAutoConfigState(target, source, Component.class);
}
private void configureAutoConfigState(Component target, ComponentContext source, Class<?> clazz) {
String name = source.getAutoConfigInstance(clazz);
if (name != null) {
target.setAutoConfig(clazz, name);
}
else {
target.setAutoConfig(clazz, source.getAutoConfig(clazz));
}
}
private final static Class[][] INIT_SIGNATUTES = new Class[][] { { Component.class }, {} };
private final static Class[][] FACTORY_CREATE_SIGNATURES = new Class[][] { {}, { Component.class } };
private boolean hasInitMethodWhenNotSingleton() {
if (m_callbackInit == null) {
return false;
}
if (m_componentDefinition instanceof Class) {
if (null != InvocationUtil.getCallbackMethod((Class<?>) m_componentDefinition, m_callbackInit, INIT_SIGNATUTES)) {
return true;
}
} else if (m_instanceFactoryCreateMethod != null) {
if (m_instanceFactory != null) {
Class<?> factoryClass = null;
if (m_instanceFactory instanceof Class) {
factoryClass = (Class<?>) m_instanceFactory;
} else if (m_instanceFactory != null) {
factoryClass = m_instanceFactory.getClass();
}
Method create = InvocationUtil.getCallbackMethod(factoryClass, m_instanceFactoryCreateMethod, FACTORY_CREATE_SIGNATURES);
Class<?> componentType = create.getReturnType();
if (componentType != null && InvocationUtil.getCallbackMethod(componentType, m_callbackInit, INIT_SIGNATUTES) != null) {
return true;
}
}
}
return false;
}
private void initPrototype() {
switch (m_scope) {
case PROTOTYPE:
case BUNDLE:
if (m_callbackInit == null || ! hasInitMethodWhenNotSingleton()) {
// when no init callback is defined, or if the component implementation does not actually has an init method, we disable injection for the component prototype instance.
m_injectionDisabled = true;
}
break;
}
}
}