blob: 9652e8f192ba6e9f4c0ba025f82ed2a2bcbde8c6 [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.TRACKING_OPTIONAL;
import static org.apache.felix.dm.ComponentState.WAITING_FOR_REQUIRED;
import java.lang.reflect.Constructor;
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.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 org.apache.felix.dm.Component;
import org.apache.felix.dm.ComponentDeclaration;
import org.apache.felix.dm.ComponentDependencyDeclaration;
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.EventType;
import org.apache.felix.dm.context.Event;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
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 {
private static final ServiceRegistration NULL_REGISTRATION = (ServiceRegistration) Proxy
.newProxyInstance(ComponentImpl.class.getClassLoader(),
new Class[] { ServiceRegistration.class },
new DefaultNullObject());
private static final Class<?>[] VOID = new Class[] {};
private volatile Executor m_executor = new SerialExecutor(new Logger(null));
private ComponentState m_state = ComponentState.INACTIVE;
private final CopyOnWriteArrayList<DependencyContext> m_dependencies = new CopyOnWriteArrayList<>();
private final List<ComponentStateListener> m_listeners = new CopyOnWriteArrayList<>();
private boolean m_isStarted;
private final Logger m_logger;
private final BundleContext m_context;
private final DependencyManager m_manager;
private Object m_componentDefinition;
private Object m_componentInstance;
private volatile Object m_serviceName;
private volatile Dictionary<Object, Object> m_serviceProperties;
private volatile ServiceRegistration m_registration;
private final Map<Class<?>, Boolean> m_autoConfig = new ConcurrentHashMap<>();
private final Map<Class<?>, String> m_autoConfigInstance = new ConcurrentHashMap<>();
private final Map<String, Long> m_stopwatch = new ConcurrentHashMap<>();
private final long m_id;
private 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<>();
private final AtomicBoolean m_active = new AtomicBoolean(false);
public Component setDebug(String debugKey) {
// Force debug level in our logger
m_logger.setEnabledLevel(LogService.LOG_DEBUG);
m_logger.setDebugKey(debugKey);
return this;
}
// configuration (static)
private volatile String m_callbackInit;
private volatile String m_callbackStart;
private volatile String m_callbackStop;
private volatile String m_callbackDestroy;
private volatile Object m_callbackInstance;
// instance factory
private volatile Object m_instanceFactory;
private volatile String m_instanceFactoryCreateMethod;
// composition manager
private volatile Object m_compositionManager;
private volatile String m_compositionManagerGetMethod;
private volatile Object m_compositionManagerInstance;
private final Bundle m_bundle;
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;
}
}
public ComponentImpl() {
this(null, null, new Logger(null));
}
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_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_callbackInit = "init";
m_callbackStart = "start";
m_callbackStop = "stop";
m_callbackDestroy = "destroy";
m_id = m_idGenerator.getAndIncrement();
}
@Override
public Component add(final Dependency ... dependencies) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
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 another component).", dc,
ComponentImpl.this);
continue;
}
m_dependencyEvents.put(dc, new ConcurrentSkipListSet<Event>());
m_dependencies.add(dc);
dc.setComponentContext(ComponentImpl.this);
if (!(m_state == ComponentState.INACTIVE)) {
dc.setInstanceBound(true);
instanceBoundDeps.add(dc);
}
}
startDependencies(instanceBoundDeps);
handleChange();
}
});
return this;
}
@Override
public Component remove(final Dependency d) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
DependencyContext dc = (DependencyContext) d;
// First remove this dependency from the dependency list
m_dependencies.remove(d);
// 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;
}
public void start() {
if (m_active.compareAndSet(false, true)) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
m_isStarted = true;
handleChange();
}
});
}
}
public void stop() {
if (m_active.compareAndSet(true, false)) {
Executor executor = getExecutor();
// First, declare the task that will stop our component in our executor.
final Runnable stopTask = new Runnable() {
@Override
public void run() {
m_isStarted = false;
handleChange();
}
};
// Now, we have to schedule our stopTask in our component executor. But we have to handle a special case:
// if the component bundle is stopping *AND* if the executor is a parallel dispatcher, then we want
// to invoke our stopTask synchronously, in order to make sure that the bundle context is valid while our
// component is being deactivated (if we stop the component asynchronously, the bundle context may be invalidated
// before our component is stopped, and we don't want to be in this situation).
boolean stopping = m_bundle != null /* null only in tests env */ && m_bundle.getState() == Bundle.STOPPING;
if (stopping && executor instanceof DispatchExecutor) {
((DispatchExecutor) executor).execute(stopTask, false /* try to execute synchronously, not using threadpool */);
} else {
executor.execute(stopTask);
}
}
}
@SuppressWarnings("unchecked")
@Override
public Component setInterface(String serviceName, Dictionary<?, ?> properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = (Dictionary<Object, Object>) properties;
return this;
}
@SuppressWarnings("unchecked")
@Override
public Component setInterface(String[] serviceName, Dictionary<?, ?> properties) {
ensureNotActive();
m_serviceName = serviceName;
m_serviceProperties = (Dictionary<Object, Object>) properties;
return this;
}
@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
getExecutor().execute(new Runnable() {
@Override
public void run() {
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;
}
}
});
}
@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);
}
public Component setAutoConfig(Class<?> clazz, boolean autoConfig) {
m_autoConfig.put(clazz, Boolean.valueOf(autoConfig));
return this;
}
public Component setAutoConfig(Class<?> clazz, String instanceName) {
m_autoConfig.put(clazz, Boolean.valueOf(instanceName != null));
m_autoConfigInstance.put(clazz, instanceName);
return this;
}
public boolean getAutoConfig(Class<?> clazz) {
Boolean result = (Boolean) m_autoConfig.get(clazz);
return (result != null && result.booleanValue());
}
public String getAutoConfigInstance(Class<?> clazz) {
return (String) m_autoConfigInstance.get(clazz);
}
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. We'll do it
// once all currently available services are found, and then after,
// we'll recalculate state change (see the startDependencies method).
//
// 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 from within
// the component start method callback (this will be extremely useful when porting the Felix SCR
// on top of DM4).
switch (m_state) {
case WAITING_FOR_REQUIRED:
if (dc.isStarted() && dc.isRequired()) {
// if dependency is starting, we'll handle change after the tracker has returned (see startDependencies method).
handleChange();
}
break;
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
if (!dc.isInstanceBound()) {
if (dc.isRequired()) {
dc.invokeCallback(EventType.ADDED, e);
}
updateInstance(dc, e, false, true);
} else {
if (dc.isStarted() && dc.isRequired()) {
// if dependency is starting, we'll handle change after the tracker has returned (see startDependencies method).
handleChange();
}
}
break;
case TRACKING_OPTIONAL:
dc.invokeCallback(EventType.ADDED, e);
updateInstance(dc, e, false, true);
break;
default:
}
}
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:
dc.invokeCallback(EventType.CHANGED, e);
updateInstance(dc, e, true, false);
break;
case INSTANTIATED_AND_WAITING_FOR_REQUIRED:
if (!dc.isInstanceBound()) {
dc.invokeCallback(EventType.CHANGED, e);
updateInstance(dc, e, true, false);
}
break;
default:
// noop
}
}
private void handleRemoved(DependencyContext dc, Event e) {
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()) {
if (dc.isRequired()) {
dc.invokeCallback(EventType.REMOVED, e);
}
updateInstance(dc, e, false, false);
}
break;
case TRACKING_OPTIONAL:
dc.invokeCallback(EventType.REMOVED, e);
updateInstance(dc, e, false, false);
break;
default:
}
}
private void handleSwapped(DependencyContext dc, Event oldEvent, Event newEvent) {
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()) {
if (dc.isRequired()) {
dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
}
}
break;
case TRACKING_OPTIONAL:
dc.invokeCallback(EventType.SWAPPED, oldEvent, newEvent);
break;
default:
}
}
private void handleChange() {
m_logger.debug("handleChanged");
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 {
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) {
// System.out.println("transition from " + oldState + " to " + newState);
if (oldState == ComponentState.INACTIVE && newState == ComponentState.WAITING_FOR_REQUIRED) {
startDependencies(m_dependencies);
notifyListeners(newState);
return true;
}
if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
instantiateComponent();
invokeAutoConfigDependencies();
invokeAddRequiredDependencies();
ComponentState stateBeforeCallingInit = m_state;
invoke(m_callbackInit);
if (stateBeforeCallingInit == m_state) {
notifyListeners(newState); // init did not change current state, we can notify about this new state
}
return true;
}
if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.TRACKING_OPTIONAL) {
invokeAutoConfigInstanceBoundDependencies();
invokeAddRequiredInstanceBoundDependencies();
invoke(m_callbackStart);
invokeAddOptionalDependencies();
registerService();
notifyListeners(newState);
return true;
}
if (oldState == ComponentState.TRACKING_OPTIONAL && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) {
unregisterService();
invokeRemoveOptionalDependencies();
invoke(m_callbackStop);
invokeRemoveInstanceBoundDependencies();
notifyListeners(newState);
return true;
}
if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.WAITING_FOR_REQUIRED) {
invoke(m_callbackDestroy);
invokeRemoveRequiredDependencies();
notifyListeners(newState);
if (! someDependenciesNeedInstance()) {
destroyComponent();
}
return true;
}
if (oldState == ComponentState.WAITING_FOR_REQUIRED && newState == ComponentState.INACTIVE) {
stopDependencies();
destroyComponent();
notifyListeners(newState);
return true;
}
return false;
}
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();
}
// The started dependencies has probably called our handleAdded method: we now have to run our state machine.
handleChange();
}
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<?,?> properties = calculateServiceProperties();
// register the service
try {
if (m_serviceName instanceof String) {
registration = m_context.registerService((String) m_serviceName, m_componentInstance, properties);
}
else {
registration = m_context.registerService((String[]) m_serviceName, m_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.ACTIVE) {
m_registration.unregister();
}
} catch (IllegalStateException e) { /* Should we really log this ? */}
autoConfigureImplementation(ServiceRegistration.class, NULL_REGISTRATION);
m_registration = null;
}
}
private Dictionary<Object, Object> calculateServiceProperties() {
Dictionary<Object, Object> properties = new Hashtable<>();
for (int i = 0; i < m_dependencies.size(); i++) {
DependencyContext d = (DependencyContext) m_dependencies.get(i);
if (d.isPropagated() && d.isAvailable()) {
Dictionary<Object, Object> dict = d.getProperties();
addTo(properties, dict);
}
}
// our service properties must not be overriden by propagated dependency properties, so we add our service
// properties after having added propagated dependency properties.
addTo(properties, m_serviceProperties);
if (properties.size() == 0) {
properties = null;
}
return properties;
}
private void addTo(Dictionary<Object, Object> properties, Dictionary<Object, Object> additional) {
if (properties == null) {
throw new IllegalArgumentException("Dictionary to add to cannot be null.");
}
if (additional != null) {
Enumeration<Object> e = additional.keys();
while (e.hasMoreElements()) {
Object key = e.nextElement();
properties.put(key, additional.get(key));
}
}
}
void instantiateComponent() {
m_logger.debug("instantiating component.");
// TODO add more complex factory instantiations of one or more components in a composition here
if (m_componentInstance == null) {
if (m_componentDefinition instanceof Class) {
try {
m_componentInstance = createInstance((Class<?>) m_componentDefinition);
}
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 {
factory = createInstance((Class<?>) m_instanceFactory);
}
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 {
m_componentInstance = InvocationUtil.invokeMethod(factory,
factory.getClass(), m_instanceFactoryCreateMethod,
new Class[][] {{}, {Component.class}}, new Object[][] {{}, {this}}, 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);
}
}
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)) {
d.invokeCallback(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)) {
d.invokeCallback(EventType.ADDED, e);
}
}
}
}
private void invokeAddOptionalDependencies() {
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
d.invokeCallback(EventType.ADDED, e);
}
}
}
}
private void invokeRemoveRequiredDependencies() {
for (DependencyContext d : m_dependencies) {
if (!d.isInstanceBound() && d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
d.invokeCallback(EventType.REMOVED, e);
}
}
}
}
private void invokeRemoveOptionalDependencies() {
for (DependencyContext d : m_dependencies) {
if (! d.isRequired()) {
for (Event e : m_dependencyEvents.get(d)) {
d.invokeCallback(EventType.REMOVED, e);
}
}
}
}
private void invokeRemoveInstanceBoundDependencies() {
for (DependencyContext d : m_dependencies) {
if (d.isInstanceBound()) {
for (Event e : m_dependencyEvents.get(d)) {
d.invokeCallback(EventType.REMOVED, e);
}
}
}
}
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);
}
}
}
@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());
}
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)) {
if (m_logger == null) {
System.out.println("Callback \"" + methodName + "\" not found on componnent instances "
+ Arrays.toString(getInstances()));
} else {
m_logger.log(LogService.LOG_ERROR, "Callback \"" + methodName + "\" callback not found on componnent instances "
+ Arrays.toString(getInstances()));
}
}
}
private Object createInstance(Class<?> clazz) throws SecurityException, NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Constructor<?> constructor = clazz.getConstructor(VOID);
constructor.setAccessible(true);
return constructor.newInstance();
}
private void notifyListeners(ComponentState state) {
for (ComponentStateListener l : m_listeners) {
l.changed(this, state);
}
}
@Override
public boolean isAvailable() {
return m_state == TRACKING_OPTIONAL;
}
@Override
public boolean isActive() {
return m_active.get();
}
@Override
public Component add(final ComponentStateListener l) {
m_listeners.add(l);
return this;
}
@Override
public Component remove(ComponentStateListener l) {
m_listeners.remove(l);
return this;
}
@SuppressWarnings("unchecked")
@Override
public List<DependencyContext> getDependencies() {
return (List<DependencyContext>) m_dependencies.clone();
}
@Override
public Component setImplementation(Object implementation) {
m_componentDefinition = implementation;
return this;
}
private 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
*/
private void configureImplementation(Class<?> clazz, Object instance, String fieldName) {
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) {
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)
{
Object[] targets = getInstances();
FieldUtil.updateDependencyField(targets, fieldName, update, add, clazz, event, dc, m_logger);
}
@Override
public ServiceRegistration getServiceRegistration() {
return m_registration;
}
@SuppressWarnings("unchecked")
@Override
public <K,V> Dictionary<K, V> getServiceProperties() {
if (m_serviceProperties != null) {
// Applied patch from FELIX-4304
Hashtable<Object, Object> serviceProperties = new Hashtable<>();
addTo(serviceProperties, m_serviceProperties);
return (Dictionary<K, V>) serviceProperties;
}
return null;
}
@Override
@SuppressWarnings("unchecked")
public Component setServiceProperties(final Dictionary<?, ?> serviceProperties) {
getExecutor().execute(new Runnable() {
@Override
public void run() {
Dictionary<Object, Object> properties = null;
m_serviceProperties = (Dictionary<Object, Object>) serviceProperties;
if ((m_registration != null) && (m_serviceName != null)) {
properties = calculateServiceProperties();
m_registration.setProperties(properties);
}
}
});
return this;
}
public Component 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 Component 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 Component setFactory(Object factory, String createMethod) {
ensureNotActive();
m_instanceFactory = factory;
m_instanceFactoryCreateMethod = createMethod;
return this;
}
@Override
public Component setFactory(String createMethod) {
return setFactory(null, createMethod);
}
@Override
public Component setComposition(Object instance, String getMethod) {
ensureNotActive();
m_compositionManager = instance;
m_compositionManagerGetMethod = getMethod;
return this;
}
@Override
public Component setComposition(String getMethod) {
return setComposition(null, getMethod);
}
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 {
instances = m_componentInstance == null ? new Object[] {} : new Object[] { m_componentInstance };
}
return instances;
}
@Override
public DependencyManager getDependencyManager() {
return m_manager;
}
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() {
StringBuffer sb = new StringBuffer();
Object serviceName = m_serviceName;
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]);
}
appendProperties(sb);
} else if (serviceName instanceof String) {
sb.append(serviceName.toString());
appendProperties(sb);
} else {
Object implementation = m_componentDefinition;
if (implementation != null) {
if (implementation instanceof Class) {
sb.append(((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)) {
sb.append(implementation.getClass().getName());
} else {
sb.append(implementation.toString());
}
} catch (java.lang.NoSuchMethodException e) {
// Just display the class name
sb.append(implementation.getClass().getName());
}
}
} else {
sb.append(super.toString());
}
}
return sb.toString();
}
private void appendProperties(StringBuffer result) {
Dictionary<Object, Object> properties = calculateServiceProperties();
if (properties != null) {
result.append("(");
Enumeration<?> enumeration = properties.keys();
while (enumeration.hasMoreElements()) {
Object key = enumeration.nextElement();
result.append(key.toString());
result.append('=');
Object value = properties.get(key);
if (value instanceof String[]) {
String[] values = (String[]) value;
result.append('{');
for (int i = 0; i < values.length; i++) {
if (i > 0) {
result.append(',');
}
result.append(values[i].toString());
}
result.append('}');
}
else {
result.append(value.toString());
}
if (enumeration.hasMoreElements()) {
result.append(',');
}
}
result.append(")");
}
}
@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 instanceFactory.getClass().getName();
} 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;
}
private Executor getExecutor() {
return m_executor;
}
}