blob: f214c460109f1640a82f12937b12a0ed1732df8e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.equinox.ds.resolver;
import java.io.IOException;
import java.util.*;
import org.eclipse.equinox.ds.Activator;
import org.eclipse.equinox.ds.Log;
import org.eclipse.equinox.ds.instance.InstanceProcess;
import org.eclipse.equinox.ds.model.*;
import org.eclipse.equinox.ds.workqueue.WorkDispatcher;
import org.eclipse.equinox.ds.workqueue.WorkQueue;
import org.osgi.framework.*;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentException;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* Resolver - resolves the Service Components. This includes creating Component
* Configurations, resolving the required referenced services, and checking for
* circular dependencies.
*
* The Resolver implements AllServiceListener so it can be informed about service
* changes in the framework.
*
* @version $Revision: 1.3 $
*/
public class Resolver implements AllServiceListener, WorkDispatcher {
/* set this to true to compile in debug messages */
private static final boolean DEBUG = false;
/**
* next free component id.
* See OSGi R4 Specification section 112.6 "Component Properties"
*/
private static long componentid;
/* ServiceTracker for configurationAdmin */
public ServiceTracker configAdminTracker;
/**
* Service Component instances need to be built.
*/
private static final int BUILD = 1;
/**
* Service Component instances to bind dynamically
*/
public static final int DYNAMICBIND = 3;
/**
* Main class for the SCR
*/
private Activator main;
public InstanceProcess instanceProcess;
/**
* List of {@link ComponentConfiguration}s - the currently "enabled"
* Component Configurations.
*/
public List enabledComponentConfigurations;
/**
* List of {@link ComponentConfiguration}s - the currently "satisfied"
* Component Configurations. Note that to be satisfied a Component
* Configuration must first be enabled, so this list is a subset of
* {@link Resolver#enabledComponentConfigurations enabledComponentConfigurations}.
*/
public List satisfiedComponentConfigurations;
/**
* A map of name:Service Component (String):({@link ComponentDescription})
*/
public Map enabledCDsByName;
private WorkQueue workQueue;
/**
* Resolver constructor
*
* @param main Main class of SCR
*/
public Resolver(Activator main) {
this.main = main;
componentid = 1;
// for now use Main's workqueue
workQueue = main.workQueue;
enabledComponentConfigurations = new ArrayList();
satisfiedComponentConfigurations = new ArrayList();
enabledCDsByName = new HashMap();
configAdminTracker = new ServiceTracker(main.context, ConfigurationAdmin.class.getName(), null);
configAdminTracker.open();
instanceProcess = new InstanceProcess(main);
//start listening to ServiceChanged events
main.context.addServiceListener(this);
}
/**
* Clean up the SCR is shutting down
*/
public void dispose() {
//stop listening to ServiceChanged events
main.context.removeServiceListener(this);
instanceProcess.dispose();
instanceProcess = null;
configAdminTracker.close();
configAdminTracker = null;
enabledComponentConfigurations = null;
satisfiedComponentConfigurations = null;
enabledCDsByName = null;
}
/**
* Enable Service Components - create Component Configuration(s) for the
* Service Components and try to satisfy their dependencies.
*
* <p>
* For each Service Component ({@link ComponentDescription}) check
* ConfigurationAdmin for properties and create a Component Configuration
* ({@link ComponentConfiguration}).
* </p>
*
* <p>
* If a {@link org.osgi.service.cm.ManagedServiceFactory ManagedServiceFactory}
* is registered for the Service Component, we may create multiple Component
* Configurations.
* </p>
*
* <p>
* After the Component Configuration(s) are created, call
* {@link Resolver#resolve(ServiceEvent) getEligible(null)} to try to
* satisfy them.
* </p>
*
* @param componentDescriptions - a List of {@link ComponentDescription}s to
* be enabled
*/
public void enableComponents(List componentDescriptions) throws ComponentException {
Iterator it = componentDescriptions.iterator();
while (it.hasNext()) {
ComponentDescription cd = (ComponentDescription) it.next();
// add to our enabled lookup list
enabledCDsByName.put(cd.getName(), cd);
// check for a Configuration properties for this component
Configuration config = null;
try {
ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) configAdminTracker.getService();
if (configurationAdmin != null) {
config = configurationAdmin.getConfiguration(cd.getName(), cd.getBundleContext().getBundle().getLocation());
}
} catch (IOException e) {
// Log it and continue
Log.log(1, "[SCR] IOException when getting Configuration Properties. ", e);
}
// if no Configuration
if (config == null) {
// create ComponentConfiguration
map(cd, null);
} else {
// if ManagedServiceFactory
if (config.getFactoryPid() != null) {
// if ComponentFactory is specified
if (cd.getFactory() != null) {
throw new ComponentException("incompatible to specify both ComponentFactory and ManagedServiceFactory are incompatible");
}
Configuration[] configs = null;
try {
ConfigurationAdmin cm = (ConfigurationAdmin) configAdminTracker.getService();
configs = cm.listConfigurations("(service.factoryPid=" + config.getFactoryPid() + ")");
} catch (InvalidSyntaxException e) {
Log.log(1, "[SCR] InvalidSyntaxException when getting CM Configurations. ", e);
} catch (IOException e) {
Log.log(1, "[SCR] IOException when getting CM Configurations. ", e);
}
// for each MSF set of properties(P), map(CD,P)
if (configs != null) {
for (int i = 0; i < configs.length; i++) {
map(cd, configs[i].getProperties());
}
}
} else {
// if Service
map(cd, config.getProperties());
}
}
}
// resolve
resolve(null);
}
/**
* Combine ConfigAdmin properties with a Service Component
* ({@link ComponentDescription}) to create a Component Configuration
* ({@link ComponentConfiguration}), and add it to our list of enabled
* Component Configurations ({@link Resolver#enabledComponentConfigurations}).
*
* The ConfigAdmin properties are combined with the properties from the
* Service Component's XML.
*
* @param cd Service Component
* @param configAdminProps ConfigAdmin properties for this Component
* Configuration
*/
public ComponentConfiguration map(ComponentDescription cd, Dictionary configAdminProps) {
return doMap(cd, configAdminProps, cd.getFactory() != null);
}
/**
* Create a Component Configuration of a Service Component that has the
* "factory" attribute.
*
* @see Resolver#map(ComponentDescription, Dictionary)
* @see ComponentConfiguration#componentFactory
*/
public ComponentConfiguration mapFactoryInstance(ComponentDescription cd, Dictionary configAdminProps) {
return doMap(cd, configAdminProps, false);
}
private ComponentConfiguration doMap(ComponentDescription cd, Dictionary configAdminProps, boolean componentFactory) {
// Create CD+P
// calculate the component configuration's properties
Hashtable properties = initProperties(cd, configAdminProps);
// for each Reference Description, create a reference object
List references = new ArrayList();
Iterator it = cd.getReferenceDescriptions().iterator();
while (it.hasNext()) {
ReferenceDescription referenceDesc = (ReferenceDescription) it.next();
// create new Reference Object
Reference ref = new Reference(referenceDesc, properties);
references.add(ref);
}
references = !references.isEmpty() ? references : Collections.EMPTY_LIST;
ComponentConfiguration componentConfiguration = new ComponentConfiguration(cd, references, properties, componentFactory);
//for each Reference, set it's "parent" (the component configuration)
it = componentConfiguration.getReferences().iterator();
while (it.hasNext()) {
Reference reference = (Reference) it.next();
// set parent component configuration
reference.setComponentConfiguration(componentConfiguration);
}
cd.addComponentConfiguration(componentConfiguration);
// add CD+P to set
enabledComponentConfigurations.add(componentConfiguration);
return componentConfiguration;
}
/**
* Initialize Properties for a CD+P
*
* The property elements provide default or supplemental property values if
* not overridden by the properties retrieved from Configuration Admin.
*
* The property and properties elements are processed in top to bottom
* order. This allows later elements to override property values defined by
* earlier elements. There can be many property and properties elements and
* they may be interleaved.
*
* @return Dictionary properties
*/
private Hashtable initProperties(ComponentDescription cd, Dictionary configAdminProps) {
Hashtable properties = new Hashtable();
// 0) add Reference target properties
Iterator it = cd.getReferenceDescriptions().iterator();
while (it.hasNext()) {
ReferenceDescription referenceDesc = (ReferenceDescription) it.next();
if (referenceDesc.getTarget() != null) {
properties.put(referenceDesc.getName() + ".target", referenceDesc.getTarget());
}
}
// 1) get properties from Service Component XML, in parse order
properties.putAll(cd.getProperties());
// 2) Add configAdmin properties
if (configAdminProps != null) {
Enumeration keys = configAdminProps.keys();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
properties.put(key, configAdminProps.get(key));
}
}
// add component.name and component.id (cannot be overridden)
properties.put(ComponentConstants.COMPONENT_NAME, cd.getName());
properties.put(ComponentConstants.COMPONENT_ID, new Long(getNextComponentId()));
// add component.factory if it's a factory
if (cd.getFactory() != null) {
properties.put(ComponentConstants.COMPONENT_FACTORY, cd.getFactory());
}
// add ObjectClass so we can match target filters before actually being
// registered
List servicesProvided = cd.getServicesProvided();
if (!servicesProvided.isEmpty()) {
properties.put(Constants.OBJECTCLASS, servicesProvided.toArray(new String[servicesProvided.size()]));
}
return properties;
}
/**
* Disable Service Components.
*
* For each Service Component ({@link ComponentDescription}),
* dispose of all of it's Component Configurations
* ({@link ComponentConfiguration}s).
*
* @see Resolver#disposeComponentConfigurations(List)
*
* @param componentDescriptions List of {@link ComponentConfiguration}s to
* disable
*/
public void disableComponents(List componentDescriptions) {
// Received list of CDs to disable
Iterator it = componentDescriptions.iterator();
while (it.hasNext()) {
// get the CD
ComponentDescription cd = (ComponentDescription) it.next();
disposeComponentConfigurations((List) ((ArrayList) cd.getComponentConfigurations()).clone());
cd.clearComponentConfigurations();
enabledCDsByName.remove(cd.getName());
}
}
/**
* Dispose of Component Configurations ({@link ComponentConfiguration}s).
*
* Remove Component Configurations from satisfied and enabled lists, and send
* to InstanceProcess to be unregistered, deactivated, and unbound.
*
* @see InstanceProcess#disposeComponentConfigurations(List)
*
* @param componentConfigurations List of {@link ComponentConfiguration}s
*/
public void disposeComponentConfigurations(List componentConfigurations) {
// unregister, deactivate, and unbind
satisfiedComponentConfigurations.removeAll(componentConfigurations);
enabledComponentConfigurations.removeAll(componentConfigurations);
instanceProcess.disposeComponentConfigurations(componentConfigurations);
}
/**
* Process a service change
* <p>
* A change has happened in the OSGi service environment, or new
* Component Configurations have been added to the system.
* </p>
* Depending on the change, take the following actions:
* <p>
* If new Component Configurations were added (param event is null):
* <ol>
* <li>Check for circularity and mark cycles</li>
* <li>Send newly satisfied Component Configurations to Instance
* process</li>
* </ol>
* </p>
* <p>
* If a service was registered:
* <ol>
* <li>Put "Dynamic Bind" events on the queue for any Component
* Configurations which should be bound to the new service</li>
* <li>Send newly satisfied Component Configurations to Instance
* process</li>
* </ol>
* </p>
* <p>
* If a service was modified:
* <ol>
* <li>Synchronously dispose of all Component Configurations that
* become unsatisfied</li>
* <li>Put "Dynamic Unbind Bind" events on the queue for any remaining
* Component Configurations which should be unbound from the service</li>
* <li>Put "Dynamic Bind" events on the queue for any Component
* Configurations which should be bound to the modified service</li>
* <li>Send newly satisfied Component Configurations to Instance
* process</li>
* </ol>
* </p>
* <p>
* If a service was unregistered:
* <ol>
* <li>Synchronously dispose of all Component Configurations that
* become unsatisfied</li>
* <li>Put "Dynamic Unbind Bind" events on the queue for any remaining
* Component Configurations which should be unbound from the service</li>
* </ol>
* </p>
*
* @param event the service event or null if new component configurations were added to the enabled list
*/
private void resolve(ServiceEvent event) {
// if added component configurations
if (event == null) {
// we added a component configuration, so check for circularity and mark
// cycles
resolveCycles();
// get list of newly satisfied component configurations and build them
List newlySatisfiedComponentConfigurations = resolveSatisfied();
newlySatisfiedComponentConfigurations.removeAll(satisfiedComponentConfigurations);
if (!newlySatisfiedComponentConfigurations.isEmpty()) {
satisfiedComponentConfigurations.addAll(newlySatisfiedComponentConfigurations); // add to satisfiedComponentConfigurations before dispatch
workQueue.enqueueWork(this, BUILD, newlySatisfiedComponentConfigurations);
}
}
// if service registered
else if (event.getType() == ServiceEvent.REGISTERED) {
// dynamic bind
List dynamicBind = selectDynamicBind(event.getServiceReference());
if (!dynamicBind.isEmpty()) {
workQueue.enqueueWork(this, DYNAMICBIND, dynamicBind);
}
// get list of newly satisfied component configurations and build them
List newlySatisfiedComponentConfigurations = resolveSatisfied();
newlySatisfiedComponentConfigurations.removeAll(satisfiedComponentConfigurations);
if (!newlySatisfiedComponentConfigurations.isEmpty()) {
satisfiedComponentConfigurations.addAll(newlySatisfiedComponentConfigurations); // add to satisfiedComponentConfigurations before dispatch
workQueue.enqueueWork(this, BUILD, newlySatisfiedComponentConfigurations);
}
}
// if service modified
else if (event.getType() == ServiceEvent.MODIFIED) {
// check for newly unsatisfied components and synchronously
// dispose them
List newlyUnsatisfiedComponentConfigurations = (List) ((ArrayList) satisfiedComponentConfigurations).clone();
newlyUnsatisfiedComponentConfigurations.removeAll(resolveSatisfied());
if (!newlyUnsatisfiedComponentConfigurations.isEmpty()) {
satisfiedComponentConfigurations.removeAll(newlyUnsatisfiedComponentConfigurations);
instanceProcess.disposeComponentConfigurations(newlyUnsatisfiedComponentConfigurations);
}
// dynamic unbind
// check each satisfied component configuration - do we need to unbind
Map dynamicUnBind = selectDynamicUnBind(event.getServiceReference());
if (!dynamicUnBind.isEmpty()) {
instanceProcess.dynamicUnBind(dynamicUnBind);
}
// dynamic bind
List dynamicBind = selectDynamicBind(event.getServiceReference());
if (!dynamicBind.isEmpty()) {
workQueue.enqueueWork(this, DYNAMICBIND, dynamicBind);
}
// get list of newly satisfied component configurations and build them
List newlySatisfiedComponentConfigurations = resolveSatisfied();
newlySatisfiedComponentConfigurations.removeAll(satisfiedComponentConfigurations);
if (!newlySatisfiedComponentConfigurations.isEmpty()) {
satisfiedComponentConfigurations.addAll(newlySatisfiedComponentConfigurations); // add to satisfiedComponentConfigurations before dispatch
workQueue.enqueueWork(this, BUILD, newlySatisfiedComponentConfigurations);
}
}
// if service unregistering
else if (event.getType() == ServiceEvent.UNREGISTERING) {
// check for newly unsatisfied components and
// synchronously dispose them
List newlyUnsatisfiedComponentConfigurations = (List) ((ArrayList) satisfiedComponentConfigurations).clone();
newlyUnsatisfiedComponentConfigurations.removeAll(resolveSatisfied());
if (!newlyUnsatisfiedComponentConfigurations.isEmpty()) {
satisfiedComponentConfigurations.removeAll(newlyUnsatisfiedComponentConfigurations);
instanceProcess.disposeComponentConfigurations(newlyUnsatisfiedComponentConfigurations);
}
// dynamic unbind
Map dynamicUnBind = selectDynamicUnBind(event.getServiceReference());
if (!dynamicUnBind.isEmpty()) {
instanceProcess.dynamicUnBind(dynamicUnBind);
}
}
}
/**
* Check if a particular component configuration is satisfied. Also checks for circularity. If
* component configuration is satisfied it is added to satisfiedComponentConfigurations list, but not sent to
* instance process
*
* @param componentConfiguration
* @return
*/
public boolean justResolve(ComponentConfiguration componentConfiguration) {
// we added a component configuration, so check for circularity and mark
// cycles
resolveCycles();
// get list of newly satisfied component configurations and build them
List newlySatisfiedComponentConfigurations = resolveSatisfied();
newlySatisfiedComponentConfigurations.removeAll(satisfiedComponentConfigurations);
if (!newlySatisfiedComponentConfigurations.contains(componentConfiguration)) {
return false;
}
satisfiedComponentConfigurations.add(componentConfiguration);
return true;
}
/**
* Calculate which of the currently enabled Component Configurations
* ({@link Resolver#enabledComponentConfigurations}) are "satisfied".
*
* <p>
* An "enabled" Component
* Configuration is "satisfied" if there is at least one OSGi Service
* registered that has the correct interface and matches the target filter
* for each of it's required (cardinality = "1..1" or "1..n") references.
* </p>
* <p>
* If a Component Configuration will register a service and security is
* enabled, check if the bundle it comes from has
* {@link ServicePermission#REGISTER} for that service. If the Component
* Configuration does not have the necessary permission it is not "satisfied".
* </p>
* @return List of {@link ComponentConfiguration}s that are "satisfied"
*/
private List resolveSatisfied() {
List resolvedSatisfiedComponentConfigurations = new ArrayList();
Iterator it = enabledComponentConfigurations.iterator();
while (it.hasNext()) {
ComponentConfiguration componentConfiguration = (ComponentConfiguration) it.next();
ComponentDescription cd = componentConfiguration.getComponentDescription();
// check if all the services needed by the component configuration are available
List refs = componentConfiguration.getReferences();
Iterator iterator = refs.iterator();
boolean hasProviders = true;
while (iterator.hasNext()) {
Reference reference = (Reference) iterator.next();
if (reference != null) {
if (reference.getReferenceDescription().isRequired() && !reference.hasProvider(componentConfiguration.getComponentDescription().getBundleContext())) {
hasProviders = false;
break;
}
}
}
if (!hasProviders)
continue;
// check if the bundle providing the service has permission to
// register the provided interface(s)
// if a service is provided
// TODO we can cache the ServicePermission objects
if (cd.getService() != null && System.getSecurityManager() != null) {
ProvideDescription[] provides = cd.getService().getProvides();
Bundle bundle = cd.getBundleContext().getBundle();
boolean hasPermission = true;
for (int i = 0; i < provides.length; i++) {
// make sure bundle has permission to register the service
if (!bundle.hasPermission(new ServicePermission(provides[i].getInterfacename(), ServicePermission.REGISTER))) {
hasPermission = false;
break;
}
}
if (!hasPermission)
continue;
}
// we have providers and permission - this component configuration is satisfied
resolvedSatisfiedComponentConfigurations.add(componentConfiguration);
} // end while (more enabled component configurations)
return resolvedSatisfiedComponentConfigurations.isEmpty() ? Collections.EMPTY_LIST : resolvedSatisfiedComponentConfigurations;
}
/**
* Listen for service change events
*
* @param event
*/
public void serviceChanged(ServiceEvent event) {
ServiceReference reference = event.getServiceReference();
int eventType = event.getType();
if (DEBUG) {
System.out.println("ServiceChanged: serviceReference = " + reference);
System.out.println("ServiceChanged: Event type = " + eventType + " , reference.getBundle() = " + reference.getBundle());
}
// if ((reference.getProperty(ComponentConstants.COMPONENT_ID) == null)
switch (eventType) {
case ServiceEvent.MODIFIED :
case ServiceEvent.REGISTERED :
case ServiceEvent.UNREGISTERING :
resolve(event);
break;
}
}
/**
* Called asynchronously by the work queue thread to perform work.
* <p>
* There are two possible work actions:
* <ul>
* <li>BUILD - workObject is a list of Component Configurations to be
* sent to the Instance process. The Component Configurations have become
* satisfied. Check that the Component Configurations are still satisfied
* (system state may have changed while they were waiting on the work
* queue) and send them to the instance process
* ({@link InstanceProcess#registerComponentConfigurations(List)}).
* </li>
* <li>DYNAMICBIND - workObject is a List of References that need to be
* dynamically bound. Check that the Component Configurations are still
* satisfied (system state may have changed while they were waiting on
* the work queue) and send them to the instance process
* ({@link InstanceProcess#dynamicBind(List)}).
* </ul>
* </p>
* @param workAction {@link Resolver#BUILD} or {@link Resolver#DYNAMICBIND}
* @param workObject a List of {@link ComponentConfiguration}s if workAction
* is {@link Resolver#BUILD} or a List of {@link Reference}s if workAction
* is {@link Resolver#DYNAMICBIND}
* @see org.eclipse.equinox.ds.workqueue.WorkDispatcher#dispatchWork(int,
* java.lang.Object)
*/
public void dispatchWork(int workAction, Object workObject) {
Iterator it;
switch (workAction) {
case BUILD :
// only build if component configurations are still satisfied
List queueComponentConfigurations = (List) workObject;
List componentConfigurations = new ArrayList(queueComponentConfigurations.size());
it = queueComponentConfigurations.iterator();
while (it.hasNext()) {
ComponentConfiguration componentConfiguration = (ComponentConfiguration) it.next();
if (this.satisfiedComponentConfigurations.contains(componentConfiguration)) {
componentConfigurations.add(componentConfiguration);
}
}
if (!componentConfigurations.isEmpty()) {
instanceProcess.registerComponentConfigurations(componentConfigurations);
}
break;
case DYNAMICBIND :
// only dynamicBind if component configurations are still satisfied
List references = (List) workObject;
it = references.iterator();
while (it.hasNext()) {
if (!this.satisfiedComponentConfigurations.contains(((Reference) it.next()).getComponentConfiguration())) {
// modifies underlying list
it.remove();
}
}
if (!references.isEmpty()) {
instanceProcess.dynamicBind(references);
}
break;
}
}
/**
* Calculate which of the currently satisfied component configurations
* ({@link Resolver#satisfiedComponentConfigurations}) need to be dynamically bound to an OSGi
* service.
*
* @param serviceReference the service
* @return a List of {@link Reference}s that need to be dynamically bound
* to this service
*/
private List selectDynamicBind(ServiceReference serviceReference) {
List bindList = new ArrayList();
Iterator it = satisfiedComponentConfigurations.iterator();
while (it.hasNext()) {
ComponentConfiguration componentConfiguration = (ComponentConfiguration) it.next();
List references = componentConfiguration.getReferences();
Iterator refIt = references.iterator();
while (refIt.hasNext()) {
Reference reference = (Reference) refIt.next();
if (reference.dynamicBindReference(serviceReference)) {
bindList.add(reference);
}
}
}
return bindList;
}
/**
* An OSGi service is unregistering, calculate which of the satisfied
* Component Configurations need to dynamically unbind from it.
* <p>
* A Component Configuration needs to dynamically unbind from a service
* if it was bound to the service and the reference it was policy="dynamic".
* </p>
* @param serviceReference
* @return a Map of {@link Reference}:{@link ServiceReference} to unbind
*/
private Map selectDynamicUnBind(ServiceReference serviceReference) {
Map unbindJobs = new Hashtable();
Iterator it = satisfiedComponentConfigurations.iterator();
while (it.hasNext()) {
ComponentConfiguration componentConfiguration = (ComponentConfiguration) it.next();
List references = componentConfiguration.getReferences();
Iterator it_ = references.iterator();
while (it_.hasNext()) {
Reference reference = (Reference) it_.next();
// Is reference dynamic and bound to this service? - must unbind
if (reference.dynamicUnbindReference(serviceReference)) {
unbindJobs.put(reference, serviceReference);
}
}
}
return unbindJobs.isEmpty() ? Collections.EMPTY_MAP : unbindJobs;
}
/**
* Doubly-linked node used to traverse the dependency tree in order to
* find cycles.
*
* @version $Revision: 1.3 $
*/
static private class ReferenceComponentConfiguration {
public Reference ref;
public ComponentConfiguration producer;
protected ReferenceComponentConfiguration(Reference ref, ComponentConfiguration producer) {
this.ref = ref;
this.producer = producer;
}
}
/**
* Check through the enabled list for cycles. Cycles can only exist if every
* service is provided by a Service Component (not legacy OSGi). If the cycle
* has no optional dependencies, log an error and disable a Component
* Configuration in the cycle. If cycle can be "broken" by an optional
* dependency, make a note (stored in the
* {@link ComponentConfiguration#delayActivateComponentConfigurationNames} List).
*
* @throws CircularityException if cycle exists with no optional
* dependencies
*/
private void resolveCycles() {
try {
// find the component configurations that resolve using other component configurations and record their
// dependencies
Hashtable dependencies = new Hashtable();
Iterator it = enabledComponentConfigurations.iterator();
while (it.hasNext()) {
ComponentConfiguration enabledComponentConfiguration = (ComponentConfiguration) it.next();
List dependencyList = new ArrayList();
Iterator refIt = enabledComponentConfiguration.getReferences().iterator();
while (refIt.hasNext()) {
Reference reference = (Reference) refIt.next();
// see if it resolves to one of the other enabled component configurations
ComponentConfiguration providerComponentConfiguration = reference.findProviderComponentConfiguration(enabledComponentConfigurations);
if (providerComponentConfiguration != null) {
dependencyList.add(new ReferenceComponentConfiguration(reference, providerComponentConfiguration));
}
} // end while(more references)
if (!dependencyList.isEmpty()) {
// component configuration resolves using some other component configurations, could be a cycle
dependencies.put(enabledComponentConfiguration, dependencyList);
} else {
dependencies.put(enabledComponentConfiguration, Collections.EMPTY_LIST);
}
} // end while (more enabled component configurations)
//traverse dependency tree and look for cycles
Set visited = new HashSet();
it = dependencies.keySet().iterator();
while (it.hasNext()) {
ComponentConfiguration componentConfiguration = (ComponentConfiguration) it.next();
if (!visited.contains(componentConfiguration)) {
List currentStack = new ArrayList();
traverseDependencies(componentConfiguration, visited, dependencies, currentStack);
}
}
} catch (CircularityException e) {
// log the error
Log.log(LogService.LOG_ERROR, "[SCR] Circularity Exception.", e);
// disable offending component configuration
enabledComponentConfigurations.remove(e.getCircularDependency());
// try again
resolveCycles();
}
}
/**
* Recursively do a depth-first traversal of a dependency tree, looking for
* cycles.
* <p>
* If a cycle is found, calls
* {@link Resolver#handleDependencyCycle(ReferenceComponentConfiguration, List)}.
* </p>
*
* @param componentConfiguration current node in dependency tree
* @param visited Set of {@link ComponentConfiguration} that are visited
* nodes
* @param dependencies Dependency tree - a Hashtable of
* ({@link ComponentConfiguration}):(List of {@link ReferenceComponentConfiguration}s)
* @param currentStack List of {@link ReferenceComponentConfiguration}s - the history of our
* traversal so far (the path back to the root of the tree)
* @throws CircularityException if an cycle with no optional dependencies is
* found.
*/
private void traverseDependencies(ComponentConfiguration componentConfiguration, Set visited, Hashtable dependencies, List currentStack) throws CircularityException {
// the component has already been visited and it's dependencies checked
// for cycles
if (visited.contains(componentConfiguration)) {
return;
}
List refComponentConfigurations = (List) dependencies.get(componentConfiguration);
Iterator it = refComponentConfigurations.iterator();
// first, add the component configuration's dependencies
while (it.hasNext()) {
ReferenceComponentConfiguration refComponentConfiguration = (ReferenceComponentConfiguration) it.next();
if (currentStack.contains(refComponentConfiguration)) {
// may throw circularity exception
handleDependencyCycle(refComponentConfiguration, currentStack);
return;
}
currentStack.add(refComponentConfiguration);
traverseDependencies(refComponentConfiguration.producer, visited, dependencies, currentStack);
currentStack.remove(refComponentConfiguration);
}
// finally write the component configuration
visited.add(componentConfiguration);
}
/**
* A cycle was detected. component configuration is referenced by the last element in
* currentStack. Throws CircularityException if the cycle does not contain
* an optional dependency, else choses a point at which to
* "break" the cycle (the break point must be immediately after an
* optional dependency) and adds a "cycle note".
*
* @see ComponentConfiguration#delayActivateComponentConfigurationNames
*/
private void handleDependencyCycle(ReferenceComponentConfiguration refComponentConfiguration, List currentStack) throws CircularityException {
ListIterator cycleIterator = currentStack.listIterator(currentStack.indexOf(refComponentConfiguration));
// find an optional dependency
ReferenceComponentConfiguration optionalRefComponentConfiguration = null;
while (cycleIterator.hasNext()) {
ReferenceComponentConfiguration cycleRefComponentConfiguration = (ReferenceComponentConfiguration) cycleIterator.next();
if (!cycleRefComponentConfiguration.ref.getReferenceDescription().isRequired()) {
optionalRefComponentConfiguration = cycleRefComponentConfiguration;
break;
}
}
if (optionalRefComponentConfiguration == null) {
// no optional dependency
throw new CircularityException(refComponentConfiguration.ref.getComponentConfiguration());
}
// add note not to initiate activation of next dependency
optionalRefComponentConfiguration.ref.getComponentConfiguration().setDelayActivateComponentConfigurationName(optionalRefComponentConfiguration.producer.getComponentDescription().getName());
}
/**
* Method to return the next available component id.
*
* @return next component id.
*/
private long getNextComponentId() {
synchronized (this) {
return componentid++;
}
}
}