blob: 69d877efc64bc2ffb03b217f7aabd69e7a31a63d [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.context;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.apache.felix.dm.ComponentDependencyDeclaration;
import org.apache.felix.dm.Dependency;
import org.apache.felix.dm.ServiceDependency;
/**
* Abstract class for implementing Dependencies.
* You can extends this class in order to supply your own custom dependencies to any Dependency Manager Component.
*
* @param <T> The type of the interface representing a Dependency Manager Dependency (must extends the Dependency interface).
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public abstract class AbstractDependency<T extends Dependency> implements
Dependency, DependencyContext, ComponentDependencyDeclaration {
/**
* The Component implementation is exposed to Dependencies through this interface.
*/
protected ComponentContext m_component;
/**
* Is this Dependency available ? Volatile because the getState method (part of the
* {@link ComponentDependencyDeclaration} interface) may be called by any thread, at any time.
*/
protected volatile boolean m_available;
/**
* Is this Dependency "instance bound" ? A dependency is "instance bound" if it is defined within the component's
* init method, meaning that it won't deactivate the component if it is not currently available when being added
* from the component's init method.
*/
protected boolean m_instanceBound;
/**
* Is this dependency required (false by default) ?
*/
protected volatile boolean m_required;
/**
* Component callback used to inject an added dependency.
*/
protected volatile String m_add;
/**
* Component callback invoked when the dependency has changed.
*/
protected volatile String m_change;
/**
* Component callback invoked when the dependency becomes unavailable.
*/
protected volatile String m_remove;
/**
* Can this Dependency be auto configured in the component instance fields ?
*/
protected volatile boolean m_autoConfig = true;
/**
* The Component field name where the Dependency can be injected (null means any field with a compatible type
* will be injected).
*/
protected volatile String m_autoConfigInstance;
/**
* Indicates if the setAutoConfig method has been invoked. This flag is used to force autoconfig to "false"
* when the setCallbacks method is invoked, unless the setAutoConfig method has been called.
*/
protected volatile boolean m_autoConfigInvoked;
/**
* Has this Dependency been started by the Component implementation ? Volatile because the getState method
* (part of the {@link ComponentDependencyDeclaration} interface) may be called by any thread, at any time.
*/
protected volatile boolean m_isStarted;
/**
* The object instance on which the dependency callbacks are invoked on. Null means the dependency will be
* injected to the Component implementation instance(s).
*/
protected volatile Object m_callbackInstance;
/**
* Tells if the dependency service properties have to be propagated to the Component service properties.
*/
protected volatile boolean m_propagate;
/**
* Tells if the dependency service properties should override default component service properties (false by default).
*/
protected volatile boolean m_propagateOverrides;
/**
* The propagate callback instance that is invoked in order to supply dynamically some dependency service properties.
*/
protected volatile Object m_propagateCallbackInstance;
/**
* The propagate callback method that is invoked in order to supply dynamically some dependency service properties.
* @see {@link #m_propagateCallbackInstance}
*/
protected volatile String m_propagateCallbackMethod;
/**
* Default empty dependency properties.
*/
protected final static Dictionary<Object, Object> EMPTY_PROPERTIES = new Hashtable<>(0);
/**
* Creates a new Dependency. By default, the dependency is optional and autoconfig.
*/
public AbstractDependency() {
}
/**
* Create a clone of a given Dependency.
* @param prototype all the fields of the prototype will be copied to this dependency.
*/
public AbstractDependency(AbstractDependency<T> prototype) {
m_instanceBound = prototype.m_instanceBound;
m_required = prototype.m_required;
m_add = prototype.m_add;
m_change = prototype.m_change;
m_remove = prototype.m_remove;
m_autoConfig = prototype.m_autoConfig;
m_autoConfigInstance = prototype.m_autoConfigInstance;
m_autoConfigInvoked = prototype.m_autoConfigInvoked;
m_callbackInstance = prototype.m_callbackInstance;
m_propagate = prototype.m_propagate;
m_propagateCallbackInstance = prototype.m_propagateCallbackInstance;
m_propagateCallbackMethod = prototype.m_propagateCallbackMethod;
m_propagateOverrides = prototype.m_propagateOverrides;
}
@Override
public String toString() {
return new StringBuilder(getType()).append(" dependency [").append(getName()).append("]").toString();
}
// ----------------------- Dependency interface -----------------------------
/**
* Is this Dependency required (false by default) ?
*/
@Override
public boolean isRequired() {
return m_required;
}
/**
* Is this Dependency satisfied and available ?
*/
@Override
public boolean isAvailable() {
return m_available;
}
/**
* Can this dependency be injected in a component class field (by reflexion, true by default) ?
*/
@Override
public boolean isAutoConfig() {
return m_autoConfig;
}
/**
* Returns the field name when the dependency can be injected to.
*/
@Override
public String getAutoConfigName() {
return m_autoConfigInstance;
}
/**
* Returns the propagate callback method that is invoked in order to supply dynamically some dependency service properties.
* @see {@link #m_propagateCallbackInstance}
*/
@Override
public boolean isPropagated() {
return m_propagate;
}
@Override
public boolean overrideServiceProperties() {
return m_propagateOverrides;
}
/**
* Returns the dependency service properties (empty by default).
*/
@SuppressWarnings("unchecked")
@Override
public <K,V> Dictionary<K, V> getProperties() {
return (Dictionary<K, V>) EMPTY_PROPERTIES;
}
// -------------- DependencyContext interface -----------------------------------------------
/**
* Called by the Component implementation before the Dependency can be started.
*/
@Override
public void setComponentContext(ComponentContext component) {
m_component = component;
}
/**
* A Component callback must be invoked with dependency event(s).
* @param type the dependency event type
* @param events the dependency service event to inject in the component.
* The number of events depends on the dependency event type: ADDED/CHANGED/REMOVED types only has one event parameter,
* but the SWAPPED type has two event parameters: the first one is the old event which must be replaced by the second one.
*/
@Override
public void invokeCallback(EventType type, Event ... events) {
}
/**
* Starts this dependency. Subclasses can override this method but must then call super.start().
*/
@Override
public void start() {
m_isStarted = true;
}
/**
* Starts this dependency. Subclasses can override this method but must then call super.stop().
*/
@Override
public void stop() {
m_isStarted = false;
m_available = false;
}
/**
* Indicates if this dependency has been started by the Component implementation.
*/
@Override
public boolean isStarted() {
return m_isStarted;
}
/**
* Called by the Component implementation when the dependency is considered to be available.
*/
@Override
public void setAvailable(boolean available) {
m_available = available;
}
/**
* Is this Dependency "instance bound" (has been defined within the component's init method) ?
*/
public boolean isInstanceBound() {
return m_instanceBound;
}
/**
* Called by the Component implementation when the dependency is declared within the Component's init method.
*/
public void setInstanceBound(boolean instanceBound) {
m_instanceBound = instanceBound;
}
/**
* Tells if the Component must be first instantiated before starting this dependency (false by default).
*/
@Override
public boolean needsInstance() {
return false;
}
/**
* Returns the type of the field where this dependency can be injected (auto config), or return null
* if autoconfig is not supported.
*/
@Override
public abstract Class<?> getAutoConfigType();
/**
* Get the highest ranked available dependency service, or null.
*/
@Override
public Event getService() {
Event event = m_component.getDependencyEvent(this);
if (event == null) {
Object defaultService = getDefaultService(true);
if (defaultService != null) {
event = new Event(defaultService);
}
}
return event;
}
/**
* Copy all dependency service instances to the given collection.
*/
@Override
public void copyToCollection(Collection<Object> services) {
Set<Event> events = m_component.getDependencyEvents(this);
if (events.size() > 0) {
for (Event e : events) {
services.add(e.getEvent());
}
} else {
Object defaultService = getDefaultService(false);
if (defaultService != null) {
services.add(defaultService);
}
}
}
/**
* Copy all dependency service instances to the given map (key = dependency service, value = dependency service properties.
*/
@Override
public void copyToMap(Map<Object, Dictionary<?, ?>> map) {
Set<Event> events = m_component.getDependencyEvents(this);
if (events.size() > 0) {
for (Event e : events) {
map.put(e.getEvent(), e.getProperties());
}
} else {
Object defaultService = getDefaultService(false);
if (defaultService != null) {
map.put(defaultService, EMPTY_PROPERTIES);
}
}
}
/**
* Creates a copy of this Dependency.
*/
@Override
public abstract DependencyContext createCopy();
// -------------- ComponentDependencyDeclaration -----------------------------------------------
/**
* Returns a description of this dependency (like the dependency service class name with associated filters)
*/
@Override
public String getName() {
return getSimpleName();
}
/**
* Returns a simple name for this dependency (like the dependency service class name).
*/
@Override
public abstract String getSimpleName();
/**
* Returns the dependency symbolic type.
*/
@Override
public abstract String getType();
/**
* Returns the dependency filter, if any.
*/
@Override
public String getFilter() {
return null;
}
/**
* Returns this dependency state.
*/
@Override
public int getState() { // Can be called from any threads, but our class attributes are volatile
if (m_isStarted) {
return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0);
} else {
return isRequired() ? ComponentDependencyDeclaration.STATE_REQUIRED
: ComponentDependencyDeclaration.STATE_OPTIONAL;
}
}
// -------------- Methods common to sub interfaces of Dependendency
/**
* Activates Dependency service properties propagation (to the service properties of the component to which this
* dependency is added).
*
* @param propagate true if the dependency service properties must be propagated to the service properties of
* the component to which this dependency is added.
* @return this dependency instance
*/
@SuppressWarnings("unchecked")
public T setPropagate(boolean propagate) {
ensureNotActive();
m_propagate = propagate;
return (T) this;
}
@SuppressWarnings("unchecked")
public T setPropagate(boolean propagate, boolean overrideServiceProperties) {
ensureNotActive();
m_propagate = propagate;
m_propagateOverrides = overrideServiceProperties;
return (T) this;
}
/**
* Sets a callback instance which can ba invoked with the given method in order to dynamically retrieve the
* dependency service properties.
*
* @param instance the callback instance
* @param method the method to invoke on the callback instance
* @return this dependency instance
*/
@SuppressWarnings("unchecked")
public T setPropagate(Object instance, String method) {
setPropagate(instance != null && method != null);
m_propagateCallbackInstance = instance;
m_propagateCallbackMethod = method;
return (T) this;
}
/**
* Sets the add/remove callbacks.
* @param add the callback to invoke when a dependency is added
* @param remove the callback to invoke when a dependency is removed
* @return this dependency instance
*/
public T setCallbacks(String add, String remove) {
return setCallbacks(add, null, remove);
}
/**
* Sets the add/change/remove callbacks.
* @param add the callback to invoke when a dependency is added
* @param change the callback to invoke when a dependency has changed
* @param remove the callback to invoke when a dependency is removed
* @return this dependency instance
*/
public T setCallbacks(String add, String change, String remove) {
return setCallbacks(null, add, change, remove);
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added or removed. They are called on the instance you provide. When you
* specify callbacks, the auto configuration feature is automatically turned off, because
* we're assuming you don't need it in this case.
*
* @param instance the instance to call the callbacks on
* @param add the method to call when a service was added
* @param remove the method to call when a service was removed
* @return this service dependency
*/
public T setCallbacks(Object instance, String add, String remove) {
return setCallbacks(instance, add, null, remove);
}
/**
* Sets the callbacks for this service. These callbacks can be used as hooks whenever a
* dependency is added, changed or removed. They are called on the instance you provide. When you
* specify callbacks, the auto configuration feature is automatically turned off, because
* we're assuming you don't need it in this case.
*
* @param instance the instance to call the callbacks on
* @param add the method to call when a service was added
* @param change the method to call when a service was changed
* @param remove the method to call when a service was removed
* @return this service dependency
*/
@SuppressWarnings("unchecked")
public T setCallbacks(Object instance, String add, String change, String remove) {
if ((add != null || change != null || remove != null) && !m_autoConfigInvoked) {
setAutoConfig(false);
}
m_callbackInstance = instance;
m_add = add;
m_change = change;
m_remove = remove;
return (T) this;
}
/**
* Returns the dependency callback instances
* @return the dependency callback instances
*/
public Object[] getInstances() {
if (m_callbackInstance == null) {
return m_component.getInstances();
} else {
return new Object[] { m_callbackInstance };
}
}
/**
* @see {@link ServiceDependency#setRequired(boolean)}
*/
@SuppressWarnings("unchecked")
public T setRequired(boolean required) {
m_required = required;
return (T) this;
}
/**
* @see {@link ServiceDependency#setAutoConfig(boolean)}
*/
@SuppressWarnings("unchecked")
public T setAutoConfig(boolean autoConfig) {
if (autoConfig && getAutoConfigType() == null) {
throw new IllegalStateException("Dependency does not support auto config mode");
}
m_autoConfig = autoConfig;
m_autoConfigInvoked = true;
return (T) this;
}
/**
* @see {@link ServiceDependency#setAutoConfig(String instanceName)}
*/
@SuppressWarnings("unchecked")
public T setAutoConfig(String instanceName) {
if (instanceName != null && getAutoConfigType() == null) {
throw new IllegalStateException("Dependency does not support auto config mode");
}
m_autoConfig = (instanceName != null);
m_autoConfigInstance = instanceName;
m_autoConfigInvoked = true;
return (T) this;
}
/**
* Returns the component implementation context
* @return the component implementation context
*/
public ComponentContext getComponentContext() {
return m_component;
}
/**
* Returns the default service, or null.
* @param nullObject if true, a null object may be returned.
* @return the default service
*/
protected Object getDefaultService(boolean nullObject) {
return null;
}
/**
* Checks if the component dependency is not started.
*/
protected void ensureNotActive() {
if (isStarted()) {
throw new IllegalStateException("Cannot modify state while active.");
}
}
}