| /* |
| * 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.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 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.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 Class<?>[] VOID = new Class[] {}; |
| |
| /** |
| * 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 = new SerialExecutor(new Logger(null)); |
| |
| /** |
| * 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 add 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 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; |
| |
| /** |
| * 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; |
| |
| /** |
| * 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; |
| } |
| } |
| |
| /** |
| * 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_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 setDebug(String debugKey) { |
| // Force debug level in our logger |
| m_logger.setEnabledLevel(LogService.LOG_DEBUG); |
| m_logger.setDebugKey(debugKey); |
| return this; |
| } |
| |
| @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; |
| } |
| |
| @Override |
| public void start() { |
| if (m_active.compareAndSet(false, true)) { |
| getExecutor().execute(new Runnable() { |
| @Override |
| public void run() { |
| m_isStarted = true; |
| handleChange(); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| 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() { |
| 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 Component setAutoConfig(Class<?> clazz, boolean autoConfig) { |
| m_autoConfig.put(clazz, Boolean.valueOf(autoConfig)); |
| return this; |
| } |
| |
| @Override |
| public Component 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()); |
| } |
| 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())); |
| } |
| |
| } |
| } |
| |
| @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; |
| } |
| |
| @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); |
| } |
| |
| @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(); |
| } |
| |
| @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; |
| } |
| |
| // ---------------------- Package/Private methods --------------------------- |
| |
| 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); |
| } |
| } |
| |
| /** |
| * Runs the state machine, to see if a change event has to trigger some component state transition. |
| */ |
| private void handleChange() { |
| 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) { |
| 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(); |
| invokeStart(); |
| invokeAddOptionalDependencies(); |
| registerService(); |
| notifyListeners(newState); |
| return true; |
| } |
| if (oldState == ComponentState.TRACKING_OPTIONAL && newState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED) { |
| unregisterService(); |
| invokeRemoveOptionalDependencies(); |
| invokeStop(); |
| invokeRemoveInstanceBoundDependencies(); |
| notifyListeners(newState); |
| return true; |
| } |
| if (oldState == ComponentState.INSTANTIATED_AND_WAITING_FOR_REQUIRED && newState == ComponentState.WAITING_FOR_REQUIRED) { |
| invoke(m_callbackDestroy); |
| removeInstanceBoundDependencies(); |
| 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 void invokeStart() { |
| invoke(m_callbackStart); |
| m_startCalled = true; |
| } |
| |
| private void invokeStop() { |
| 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()) { |
| if (dc.isRequired()) { |
| invokeCallbackSafe(dc, EventType.ADDED, e); |
| } |
| updateInstance(dc, e, false, true); |
| } else { |
| if (dc.isStarted() && dc.isRequired()) { |
| handleChange(); |
| } |
| } |
| break; |
| case TRACKING_OPTIONAL: |
| invokeCallbackSafe(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: |
| invokeCallbackSafe(dc, EventType.CHANGED, e); |
| updateInstance(dc, e, true, false); |
| break; |
| |
| case INSTANTIATED_AND_WAITING_FOR_REQUIRED: |
| if (!dc.isInstanceBound()) { |
| invokeCallbackSafe(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) { |
| 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()) { |
| invokeCallbackSafe(dc, EventType.REMOVED, e); |
| } |
| updateInstance(dc, e, false, false); |
| } |
| break; |
| case TRACKING_OPTIONAL: |
| invokeCallbackSafe(dc, 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 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<?,?> 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)); |
| } |
| } |
| } |
| |
| 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)) { |
| invokeCallbackSafe(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)) { |
| invokeCallbackSafe(d, EventType.ADDED, e); |
| } |
| } |
| } |
| } |
| |
| private void invokeAddOptionalDependencies() { |
| for (DependencyContext d : m_dependencies) { |
| if (! d.isRequired()) { |
| for (Event e : m_dependencyEvents.get(d)) { |
| invokeCallbackSafe(d, EventType.ADDED, e); |
| } |
| } |
| } |
| } |
| |
| private void invokeRemoveRequiredDependencies() { |
| for (DependencyContext d : m_dependencies) { |
| if (!d.isInstanceBound() && d.isRequired()) { |
| for (Event e : m_dependencyEvents.get(d)) { |
| invokeCallbackSafe(d, EventType.REMOVED, e); |
| } |
| } |
| } |
| } |
| |
| private void invokeRemoveOptionalDependencies() { |
| for (DependencyContext d : m_dependencies) { |
| if (! d.isRequired()) { |
| for (Event e : m_dependencyEvents.get(d)) { |
| invokeCallbackSafe(d, EventType.REMOVED, e); |
| } |
| } |
| } |
| } |
| |
| private void invokeRemoveInstanceBoundDependencies() { |
| for (DependencyContext d : m_dependencies) { |
| if (d.isInstanceBound()) { |
| for (Event e : m_dependencyEvents.get(d)) { |
| invokeCallbackSafe(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 invokeCallbackSafe(DependencyContext dc, EventType type, Event event) { |
| if (! dc.isRequired() && ! m_startCalled) { |
| return; |
| } |
| if (m_invokeCallbackCache.put(event, event) == null) { |
| dc.invokeCallback(type, event); |
| } |
| } |
| |
| /** |
| * 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); |
| 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 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); |
| } |
| } |
| |
| 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); |
| } |
| |
| 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; |
| } |
| |
| 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(")"); |
| } |
| } |
| |
| private Executor getExecutor() { |
| return m_executor; |
| } |
| } |