58 Resolver Service Specification

58.1 Introduction

Today very few applications are self contained, the predominant development model is that applications are built from (external) components, which are often open source. Application developers add business logic, glue code, and assemble the diverse components into a resource that provides the desired capabilities when installed in an environment. Designing the assembly has long been a manual and error prone process, partly due to the complexity of external dependencies. Although the direct dependencies are often given, the largest number of dependencies are usually the transitive dependencies: the dependencies of the dependencies. Modern applications can end up with hundreds to thousands of external dependencies. Numbers that make tooling inevitable.

The OSGi framework is the first specification that provides a foundation for automating a significant part of this assembly process. The Requirement-Capability model defined in Resource API Specification provides a dependency model that allows resources to express dependencies, constraints, and capabilities. If a resource's constraints are met it provides capabilities that can satisfy further requirements. The OSGi dependency model is fully generic and is not limited to bundles. Resources can be bundles but also certificates, plugged in devices, etc.

Resolving transitive dependencies is a non-trivial process that requires careful design to achieve the required performance since the underlying problem is NP-complete. OSGi frameworks have always included such resolvers but these were built into the frameworks. They were not usable outside the framework for tooling, for example automatically finding the dependencies of a bundle that needs to be installed.

The number of dependencies is rapidly reaching a threshold where manual methods no longer can provide reliable results. This specification therefore provides the Resolver service, a service that can be the base for provisioning, deployment, build, and diagnostic tooling. The service can take a requirement and resolve it to a wiring of resources. For example, with cloud computing a new requirement can be translated into a new OSGi framework instance being started on a node and provisioned with the set of bundles that satisfy the given requirement. The OSGi Resolver service is intended be a corner stone of such an auto-provisioning tool.

However, the OSGi Resolver service is not limited to these higher end schemes. Build tools can use the Resolver to find components for the build path and/or run time environment and predict the results of installing a set of bundles on a target environment. The OSGi Resolver service is an essential part of a software model where applications are built out of independent components.

This specification is based on the concepts and API defined in the Resource API Specification, Bundle Wiring API Specification, and the Module Layer. These specifications are required reading for understanding this specification. This specification is for highly specialized use, it is not intended to be used in applications, the Resolver API is a low level service intended for system developers with deep knowledge of the OSGi module layer.

58.1.1 Essentials

  • Transitive - From a requirement, find a consistent set of resources that satisfy that requirement.

  • Diagnostics - Provide diagnostic information when no resolution can be found.

  • Scoped Repositories - Allow the environment to control the repositories to use.

  • Build Tools - Must be useful in establishing build and run time class paths.

  • Provisioning - Must be useful to find a set of bundles that can be installed in a system without running into unresolved dependencies.

  • OSGi - Provide the semantics of all the OSGi namespaces, including the uses constraints.

  • API - The API for the Resolver must provide the base for the Framework Bundle Wiring API.

  • Performant - Enable highly performant implementations.

  • Frameworks - Allow Frameworks to provide their resolver as a service.

  • Scalable - Allow access to, and use of, very large repositories.

58.1.2 Entities

  • Environment - A container or framework that can install resources and uses a Resolver to wire these resources.

  • Resolve Context - An interface implemented by the management agent to provide the context of the resolution.

  • Wiring - Represents the state of a resource's wires, requirements, and capabilities in an environment.

  • Resolver - A service that can find a set of wires that can be applied to an existing wiring state where a set of initial resources have all their mandatory requirements satisfied.

  • Wire - Links requirement to a capability.

  • Resource -An artifact with requirements that need to be provisioned and resolved to provide its capabilities.

  • Requirement - A necessity for a given resource before it can provide its capabilities; expressed as a filter expression on the attributes of a capability in a given namespace.

  • Capability - A set of attributes and directives defined by a namespace, describes what a resource provides when resolved.

  • Hosted Capability - Pairs a resource with a capability to model hosting capabilities in another resource.

  • Namespace - The type for a capability and requirement.

  • Resolution - The result of a resolve operation.

Figure 58.1 Class and Service overview

Class and Service overview

58.1.3 Synopsis

The Resolver service can find a complete and consistent set of transitive dependencies starting with an initial set of mandatory and optional resources. Such a set can be used to install resources in the environment from local or remote repositories. To use the Resolver service, the client must provide a ResolveContext object. This complex object represents the context of a resolution; it provides the initial resources (optional and mandatory), defines a number of namespaces specific policies, and provides the state of the environment.

A resolution can be based on an existing wiring in the environment, for example the current framework state. For the framework, the Resolve Context can find this existing state via the Bundle Wiring API Specification. The Resolver must then satisfy all the requirements of the mandatory resources. The Resolver must always ask the Resolve Context to find additional capabilities for the unsatisfied requirements. A capability is always associated with a resource, which is subsequently associated with additional requirements. The final resolution must contain a set of resources that include the initial set of mandatory resources, has no unsatisfied mandatory requirements, and is consistent with the implied constraints. Otherwise it fails.

The Requirement-Capability model is fully generic but provides special semantics through the use of namespaces. The Resolver must implement the standard OSGi namespaces as described in Bundle Wiring API Specification, which includes the uses constraints. Part of the semantics of the OSGi namespaces are delegated to the Resolve Context so that it can implement different policies. Singletons, ordering of capabilities, and matching are the responsibility of the Resolve Context; the Resolver never matches a requirement to a capability.

Requirements have an effective directive that indicates in what situations the requirement must be resolved. Also here, the Resolve Context decides if a particular requirement is effective or not. Only effective requirements are wired in the resolution.

Since capabilities are declared by resources that have additional requirements, the Resolver must create a set of resources where all transitive requirements are satisfied or fail with a Resolution Exception. This Resolution Exception can provide additional details why the resolution failed, if possible.

At the end of a successful resolve operation the Resolver returns a Map<Resource,List<Wire>>. These wires are a delta on the existing state, if any. The wires can then be used to provision missing resources or to provide diagnostic feedback.

58.2 The Resolve Context

Provisioning is the process of providing a framework with the necessary resources to allow it to operate according to set goals. In OSGi terms, this consists of installing bundles and ensuring that the configuration is set up correctly. With OSGi, bundles explicitly describe their capabilities and requirements as manifest headers. This can range from Export-Package (a capability) to a generic Provide-Capability header.

OSGi Frameworks have a resolving stage that ensures requirements are satisfied before a bundle is allowed to provide code to the shared space. As long as the requirements are not met, the bundle remains in the INSTALLED state and is thus prohibited from contributing capabilities. Once all the mandatory requirements are met, the bundle becomes RESOLVED. That is, a framework combines two decisions when it resolves bundles:

  • Find a resolution based on the existing set of installed bundles.

  • Move the bundles that have all their mandatory requirements satisfied to the RESOLVED state.

The Resolver service separates these two stages and thus allows a third party, the management agent, to define the environment of the resolution. A management agent can interact with the Resolver service while it is searching for a resolution because the Resolver service calls back the management agent through a ResolveContext object. The Resolver service will therefore allow the management agent to handle more scenarios, better diagnostics, etc.

The Resolve Context is provided by the management agent, it is an abstract base class and must therefore be extended. It is a non-trivial class to implement since it is tightly coupled to the rules of the environment; it represents the policies of the management agent for that environment. For OSGi framework resolvers, the Resolve Context must understand and implement a part of the OSGi framework namespaces.

With the Resolver service, a management agent can try out resolutions before any bundle can see the result of such a resolution but it can also include extra bundles or other resources on demand. The Resolver service will also allow resolutions to be calculated for other frameworks.

For example, a management agent could use a Resolver service to find missing dependencies and install them on demand from a local directory with bundles. Such a Provisioner could have the following skeleton:

public class Provisioner {
  File                 bundles   = ...;
  Map<String,Resource> resources = ...;
  Resolver             resolver  = ...;
  BundleContext        context   = ...;

  public void install(String location) {
    Resource resource = resources.get( location );
    if ( resource == null ) error(...);
    
    try {
       ResolveContextImpl rc = ...
       rc.addMandatory( resource );
      Set<Resource> provision = resolver.resolve( rc ).keySet();

      for ( Resource rb : provision ) {
        String location = getLocation( rb );
        
        Bundle bundle = context.installBundle( location );
            if ( !isFragment( bundle ) )
            bundle.start();
      }
    } catch(ResolutionException re) {
      ... // diagnostics
    } catch(BundleException be) {
      ... // diagnostics
    }
  }
}

58.2.1 Mandatory and Optional Resources

The Resolve Context provides all the parameters for the resolve operation, the Resolver does not maintain any state between invocations. The Resolve Context must therefore provide the mandatory and optional resources, which are essentially the input parameters to the resolve operation. The resolver must find a solution that includes at least the initial mandatory resources and should include the optional resources.

58.2.2 Finding Capabilities

The Resolve Context's findProviders(Requirement) method must be implemented in such a way that it returns an ordered list of capabilities. The Resolver will treat the order of the capabilities as preferences, the first element is more preferred than a later element. The Resolver cannot guarantee that the wiring obeys this preference since there can be other constraints. However, a Resolver must use this preference order for simple cases and try to use it in more constrained situations.

The Resolver does not make any assumptions, this means that the findProviders(Requirement) method must do all the matching. Even though the Resolver gets the mandatory and optional resources it will not search these for capabilities to satisfy requirements. If the findProviders(Requirement) method does not search these resources then their capabilities will not be used. The same is true for the existing wiring state used.

Since this section describes the Resolver with respect to a provisioning agent, the set of resources is not limited to the installed set. That is, normally when a framework is resolved the Resolver only has to include installed resources. However, for a provisioning agent it is possible to retrieve external resources. The [1] Repository Service Specification provides access to resource repositories but a management agent is free to find capabilities by any alternative means.

For resolving an OSGi framework the specifications outlines a number of heuristics that guide the order of wiring bundles and packages:

  1. A resource that is already resolved, that is, it is already wired

  2. The highest version

  3. The lowest bundle id

The Resolver can, and likely will, use the returned list to maintain its internal state during the resolve operation while trying out different potential solutions. It can add and remove capabilities at will. The returned list must therefore be mutable and not reused, after it is returned; it becomes owned by the Resolver. However, the Resolver can call back later with the insertHostedCapability(List,HostedCapability) method, giving back the returned list as the first parameter, see Insert Hosted Capabilities.

For example, assume that all possible resources in the previous example can be gotten with the getSortedResources method in the preferred resource order. This list contains all installed resources as well as any potentially installable resources. This list is sorted once on the given ordering criteria, this only has to be done once and not for each findProviders(Requirement) method invocation. The following code, which does not order by capability versions, could then be a simple skeleton for the findProviders(Requirement) method on the ResolveContextImpl inner class:

public List<Capability> findProviders(Requirement requirement ) {
  List<Capability> result = new ArrayList<Capability>();

  for ( Resource r : getSortedResources() )
    for ( Capability c : r.getCapabilities( null ) )
      if ( match( requirement, c ) )
          result.add( c );

  return result;
}

58.2.3 Matching

The findProviders(Requirement) method is responsible for only returning the capabilities that the management agent wants to be considered by the Resolver. Since the Resolver must never match any requirements to capabilities it allows the management agent to fully control the matching. However, in an OSGi environment the following matching behavior is expected:

  • Requirements and capabilities must be in the same namespace.

  • Only requirements and capabilities that have no effective directive or have the directive set to resolve should be considered.

  • The requirement's filter must match the capability's attributes.

  • If the namespace is an osgi.wiring.* namespace then the mandatory directive on the capability must be supported. Mandatory attributes are defined with a mandatory directive on a capability, they contain a list of attribute names. Each of these attributes must be used in the filter. Since the filter must be constructed from the corresponding manifest header it is sufficient to search the filter string with a regular expression that detects the usage of an attribute name.

The following example shows a skeleton match method that provides OSGi semantics:

boolean match(Requirement r, Capability c){
  if ( !r.getNamespace().equals( c.getNamespace() ) )
    return false;

  String effective = c.getDirectives().get("effective");
  if ( !(effective == null || effective.equals( "resolve") ) ) 
    return false;

  String f = r.getDirectives().get( "filter" );
  if ( f  != null ) {
    Filter filter = context.createFilter( f );
    if ( !filter.matches( c.getAttributes() ) )
      return false;
  }

  if ( !c.getNamespace().startsWith( "osgi.wiring." ) )
    return true;

  String mandatory = c.getDirectives().get("mandatory");
  if ( mandatory == null)
    return true;

  List<String> attrs = 
    Arrays.asList( mandatory.toLowerCase().split( "\\s*,\\s*") );

  Matcher m = FILTER_ASSERT_PATTERN.matcher( f == null ? "": f );
  while( m.find() )
    attrs.remove(m.group(1)); // the attribute name

  return mandatory.isEmpty();
}

58.2.4 Repositories

Resolving to provision a framework is different than a framework resolving itself. During provisioning remote repositories can be consulted to find external resources while the framework only resolves a limited set (the installed bundles). These repositories generally contain magnitudes more bundles than what is installed in a framework.

Repositories do not implement any namespace specific semantics and therefore do not understand any directives. Repositories only verify the requirement's filter (if any) against the capability's attributes. The Resolver expects the Resolve Context to do the namespace specific matching. The [1] Repository Service Specification provides the details for a service that abstracts a Requirement-Capability aware repository.

With such a repository service the findProviders(Requirement) method can be implemented as follows:

List<Repository> repositories = new CopyOnWriteArrayList<Repository>();

void addRepository(  Repository repository) { repositories.add(repository);}
void removeRepository(Repository repository){ repositories.remove(repository);}

public List<Capability> findProviders( Requirement requirement) {
  List<Capability> result = new ArrayList<Capability>();

  // previous findProviders that searches the initial resources

  for ( Repository repository : repositories ) {
     Collection<Capability> capabilities = repository.findProviders(
            Collections.singleton( requirement ) ).get( requirement);
     for ( Capability c : capabilities )
       if ( match( requirement, c ) )
           result.add( c );
  }
  return result;
}

58.2.5 Existing Wiring State

The Resolver service always creates a list of wires that should be added to an existing state. To get the existing state, the ResolveContext interface specifies the getWirings() method. This method must return the existing state as a Map<Resource,Wiring>. A Wiring is an object that reflects the wired state of a resource in the environment. From this object, all declared and hosted capabilities and requirements can be found, including their wires if any. The Resolver needs this existing state to create a consistent resolution. For example, uses constraints require access to the existing state.

The Resolver service API is based on the generic Requirement-Capability model. This API is implemented by the OSGi framework to reflect its internal wiring, see Bundle Wiring API Specification. When the Resolver service is used for an OSGi framework then the Resolve Context can provide the existing wiring state based on the Framework Wiring API. The interfaces used in the org.osgi.framework.wiring package all extend their counterpart in the org.osgi.resource package (the generic model). For example, the BundleCapability interface extends the Capability interface.

The framework wiring API models all the power and complexities of the OSGi framework. One of those aspects is removal pending. Each installed bundle is represented by one or more bundle revisions. Each bundle revision is a Resource object but only one is the current bundle revision. During a resolve operation a framework can actually wire to the current bundle revision but is not forbidden to also select the pending removal bundle revisions. The Resolve Context must therefore decide if it provides only the current bundle revisions or all. The best policy solution in this case is to always refresh after a (batch) of install operations and only resolve when there are no pending-removal bundle revisions. However, certain management agents attempt to manage a system that is in this half-way state and will then be required to include the pending-removal revisions.

The following example code shows a possible implementation of the getWirings() method. It only uses the current wiring and ignores removal pending bundle revisions:

public Map<Resource,Wiring> getWirings(){
    Map<Resource,Wiring> wirings = new HashMap<Resource,Wiring>();

   for ( Bundle b : context.getBundles() ) {
     BundleRevision revision = b.adapt( BundleRevision.class);
     if ( revision != null ) {
       Wiring wiring = revision.getWiring();
       if ( wiring != null )
           wirings.put( revision, wiring );
     }
   }
   return wirings;
}

A wiring for a resource may also have a subset of required wires which substitute capabilities provided by the resource or one of its attached fragment resources. The getSubstitutionWires(Wiring) method is called by the resolver in order to find the wires that substitute capabilities of the wiring. For example, when a wiring provides a osgi.wiring.bundle capability that is used to resolve one or more osgi.wiring.bundle requirements. In this case the resolver needs to discover which capabilities have been substituted in order to ensure a consistent class space (see Requiring Bundles). In order to get the capabilities which have been substituted, the resolver asks the resolve context to return the substitution wires for the wiring.

Note that the default implementation of this method searches all the osgi.wiring.package capabilities which are declared as provided by the resource associated with the wiring and fragment resources wired to the wiring with the osgi.wiring.host namespace. The provided capabilities are compared against the required package wires to determine which wires are substitution wires. Subclasses of ResolveContext should provide a more efficient implementation of this method.

58.2.6 Effective

The Resolver service is designed to work with OSGi frameworks but the scope is broader; its design allows many alternative usages. The effective directive on the capabilities and requirements is meant to allow requirements and capabilities to be effective in different phases. The default is resolve, the value for an OSGi framework resolving process. Bundles and other OSGi defined artifacts must declare their capabilities and requirements in the resolve effective time to be considered by an OSGi resolver.

However, Resolvers can use the effective directive to run the Resolver at other times than the standard OSGi framework resolve. For example, it could be possible to define an active time to resolve the service dependencies.

For this reason, the Resolver is designed to be agnostic for this directive, it will always ask the ResolveContext if a requirement is effective. It does this with the isEffective(Requirement) method. Since the Resolver service never matches requirements to capabilities it is also up to the Resolve Context to decide how to treat the effective directive. For an OSGi resolve operation, capabilities should also have an effective time value of resolve (or not set since resolve is the default).

To make requirements effective during the resolving of bundles it will be necessary to implement the isEffective(Requirement) method similar to:

public boolean isEffective( Requirement requirement) {
  String e = requirement.getDirectives().get( "effective");
  return e==null || "resolve".equals( e );
}

58.2.7 Insert Hosted Capabilities

One of the complex aspects of resolving for an OSGi framework is handling fragments. For fragments, the declared capabilities are going to be hosted by their hosts. The Requirement and Capability objects have a getResource method that returns the associated resource. For hosted capabilities and requirements this must be the hosting resource and for others the declaring resource.

The HostedCapability interface defines the interface for allowing the hosting resource to be returned instead of the declaring resource. Since the Resolver service creates these Hosted Capabilities the Resolver needs a way to add them to the lists of capabilities returned from findProviders(Requirement). The Resolver service cannot add them itself since this list has a preference order, the Resolver service must therefore ask the Resolve Context to insert this new capability to allow the Resolve Context to maintain the desired order.

The Resolve Context must therefore implement an insertHostedCapability(List,HostedCapability) method. The given list must have been returned earlier from a findProviders(Requirement) method invocation. The Resolve Context must find the appropriate position to insert the HostedCapability object, insert it, and return the index of the inserted object.

It is the responsibility of the Resolve Context to find the proper position. In Finding Capabilities it was discussed how the findProviders(Requirement) method must return an ordered list. The insertHostedCapability(List,HostedCapability) has that same responsibility.

The following example shows how the Hosted Capability is inserted based on the index of the hosted resource's index in the sorted list of resources the management agent maintained. The example iterates through the capabilities and compares the index of sorted resources to indicate preference. If it finds a capability from a resource that was later in the list of sorted resources then it inserts it at that position. A real implementation should also take the version of the capability into account.

public int insertHostedCapability( 
    List<Capability> caps, HostedCapability hc ) {

    List<Resource> resources = getSortedResources();
    int index = resources.indexOf( hc.getResource() );

    for ( int i =0; i < caps.size(); i++ ) {
        Capability c = caps.get( i );
        int otherIndex = resources.indexOf( c.getResource() );
        if ( otherIndex > index ) {
            caps.add( i, hc );
            return i;
        }
    }
    caps.add( hc );
    return caps.size()-1;
}

58.2.8 Fragments

Fragments are resources that have an osgi.wiring.host requirement that must match a capability from one or more host bundles. However, for example an Export-Package in a fragment must be merged with its attached hosts. These capabilities and requirements from namespaces that appear as if they come from the host bundle are called hosted.

When resolving a set of resources it must be possible to pull in any available fragments which may attach to the resource. Since fragments are not required by the host bundle, there will be no resource requiring the fragment bundles. However, fragments will require their hosts. A Resolver should attach any fragments available in a resolution to suitable hosts.

In order to discover additional fragments which may be attached to the resources in a resolution the findRelatedResources(Resource) method is called by the resolver . The resolver attempts to also resolve the related resources during the current resolve operation. Failing to resolve one of the related resources must not result in a resolution exception unless the related resource is also considered a mandatory resource.

A resolve context may consider a fragment to be a related resource for the hosts it can attach to. In order for the resolver to pull the fragments into the resolve operation the resolve context is asked to return the related resources of each host bundle which is to be resolved. The resolve context may decide if the fragments of the host needs to be resolved along with the host. Note that fragments are used as an example of a related resource. The resolve context is free to use any type of resource as a related resource.

Fragments can of course also be found by the normal finding of capabilities.

58.2.9 Singleton Capabilities

A resource can be marked as a singleton. A singleton resource has the singleton directive set to true on the osgi.identity capability. A singleton resource conflicts with another singleton resource if:

  • They have the same osgi.identity, and

  • They have the same type, and

  • They have a different or identical version.

This constraint is not enforced by the Resolver service to give more flexibility to management agents. The Resolve Context must ensure that it does not return capabilities from conflicting singleton resources from the findProviders(Requirement) method. When the Resolver is used with a limited set of resources then it is possible to enumerate all singletons ahead of time and use permutations. However, when repositories are used an incremental method works better because the scope is so much larger.

When the findProviders(Requirement) method is called for a requirement that selects a capability declared in a singleton then it is likely that repositories will return multiple versions of this singleton including the resource with the highest available version for conflicting resources. It is therefore possible to maintain a white list of singletons incrementally.

Once the findProviders(Requirement) method has created a result list, it is possible to prune this list of conflicting singletons. This can be an incremental process, the first time a singleton is encountered in such a list of capabilities the highest version can be selected as the singleton. Other singletons that are in that list or come in other invocations of findProviders(Requirement) can then no longer provide capabilities. For example:

    Map<String,Resource> whitelist = new HashMap<String,Resource>();
    
    void prune( List<Capability> list ) {
        Map<String,Resource> singletons = new HashMap<String,Resource>();

        for ( Capability c : list) {
            Resource r = c.getResource();
            Version now = getVersion( r );
            String identity = getIdentity( r );

            if ( isSingleton( r ) && !whitelist.containsKey(identity ) ) {
                Resource selected = singletons.get( identity );
                if ( selected == null )
                    singletons.put( identity, r );
                else {
                    Version old = getVersion( selected );
                    if ( now.compareTo( old )> 0 )
                        singletons.put( identity, r );
                }
            }
      }

      this.whitelist.putAll( singletons );
        
        for ( Iterator<Capability> i=list.iterator(); i.hasNext();) {
            Capability c = i.next();
            Resource r = c.getResource();
            String identity = getIdentity( r );
            Resource selected = this.whitelist.get( identity );
            if ( selected != null && !selected.equals( r ))
                i.remove();
        }
  }

58.2.10 Diagnostics

The Resolver service throws a ResolutionException when the resolve operation cannot find a solution. This Exception provides the standard human readable message. However, there is also the getUnresolvedRequirements() method. With this method it is possible to find what requirements could not be matched. Though this is very useful in many cases it must be realized that resolving is a complicated process. It is not possible to establish the exact set of missing requirements because any unresolved resolution can have many, different, sets of unresolved requirements. This is an intrinsic part of the resolution problem. There is also no guarantee that providing capabilities that satisfy these requirements will give a successful resolution. There could still be other constraints that cannot be satisfied. In general, the Resolve Context can already detect any unresolved mandatory requirements when it cannot find a matching capability in the findProviders(Requirement) method.

That said, the getUnresolvedRequirements() can often point to a potential solution.

58.2.11 Cancel

Some resolution operations can be long running, and therefore a resolve context may want to cancel the currently running resolve operation. The resolver must register a single runnable callback with the resolve context that is associated with the currently running resolve operation. The onCancel(Runnable) method must be invoked by the resolver implementation before any other method on the resolve context is invoked. The resolve context invokes the callback to cancel the currently running resolve operation that appears to be running endlessly or at risk of running out of resources. The resolve context may give up on the resolve operation or attempt to try another resolve operation with a smaller set of resources which may allow the resolve operation to complete normally.

58.2.12 Complexity

Implementing a Resolve Context is a non-trivial task that requires extensive knowledge of the OSGi framework, especially the module layer. Though this section contains numerous code examples, they are not sufficient to implement a real Resolve Context since this would require too much code for a specification.

58.3 Resolver Service

The Resolver service is an interface to a generic constraint solver based on the Require-Capability model defined in Resource API Specification. This model defines a constraint-solving language that is used by the Framework, see Module Layer, to create the mesh of class loaders. However, the Resolver service has been designed to be useful in solving other types of constraint problems.

The task of the Resolver is to find a resolution. The resolve method returns a delta on an existing wiring state. The total of existing wiring state and the applied delta is the resolution. The delta is a set of wires between requirements and capabilities.

58.3.1 Variables

The resolve(ResolveContext) method uses a Resolve Context to provide the context and parameters of the resolution. During the resolution process the Resolver service can callback the Resolve Context to retrieve the following information:

Rm Collection<Resource> getMandatoryResources()
Ro Collection<Resource> getOptionalResources()
Cenv Map<Requirement,List<Capability>> Combined answers from the findProviders(Requirement) method
Qeff Collection<Requirement> Set of effective requirements as defined by the isEffective(Requirement) method
X Map<Resource,Wiring> An existing Wiring state, getWirings()
S Map<Wiring,List<Wire>> The substitution wires of an existing Wiring state, getSubstitutionWires(Wiring)

The Resolver service returns the following:

D Map<Resource,List<Wire>> The resolution, a delta on the existing state

The resolve(ResolveContext) method returns a resolution D that is a delta on the existing Wiring state X. It is up to the Resolve Context to ensure that the delta D is installed. In for example the OSGi framework the framework hooks can be used to guide the framework's resolving process.

58.3.2 Resolving

The goal of the Resolver is to provide a set of wires between a set of resolved resources. A resource is resolved when all its mandatory and effective requirements are satisfied by capabilities from resolved resources. A Resolver must not return wires between resources that have unsatisfied mandatory requirements.

A mandatory requirement has a resolution directive that is not set or that is set to mandatory. The effectiveness of a requirement is defined by the Resolve Context, a Resolver service must call the isEffective(Requirement) method to establish if a requirement is effective for a given resolve operation. A Resolver must never create a wire from a requirement that is not effective.

To find a resolution, the Resolver must use the Resolve Context to find candidate capabilities for the requirements declared in the resources that it needs to resolve. A candidate capability is a capability that satisfies the requirement. From the perspective of the Resolver service, a requirement is satisfied by a capability when that capability is returned from the findProviders(Requirement) method. A Resolver has no alternative way to find out if a requirement is satisfied by a capability and must therefore not make any assumptions about matching. Any matching rules like for example the osgi.wiring.* mandatory directive must be implemented by the Resolve Context. A Resolve Context must always return the same capabilities during a resolving operation when given the same requirement.

Since the resolver cannot match requirements and capabilities the Resolve Context must return capabilities for all possible resources, this must include:

  • The given mandatory resources R m

  • The given optional resources R o

  • The existing Wiring state X

It can include additional resources that were indirectly returned through the findProviders(Requirement) method.

The existing wiring X and its substitution wires S provides an existing set of constraints that the Resolver service must abide by. It can get this state with the getWirings() and getSubstitutionWires(Wiring) methods. The purpose of the existing state is to ensure that any wires are compatible with existing wiring. For an OSGi framework it is crucial that the uses constraints are maintained for the resolution, see Module Layer.

The Resolver service can wire new requirements to existing capabilities but it can never create wires for an existing requirement from the existing wiring unless the resolve process is for a dynamic resolve, see Dynamic Resolving.

If the Resolver service attaches a hosted resource like a fragment, and thereby needs to add new HostedCapability objects to a previously returned list from findProviders(Requirement) then it must call the Resolve Context's insertHostedCapability(List,HostedCapability) method.

Fragments can be attached to resolved resources or to resources that declare the capabilities returned from findProviders(Requirement), that is, Cenv. Additional resources are also pulled into the resolve operation by invoking the findRelatedResources(Resource) method on the resolve context. As part of the related resources the resolve context can include fragments to be considered as part of the resolve operation. This allows the available fragments to be resolved when the host is being resolved.

This specification does not define the detailed rules and constraints associated with resolving; these rules are defined in their namespaces. An OSGi Resolver service must support at least all namespaces defined in Framework Namespaces Specification except for the following directives:

  • mandatory - Mandatory attributes on the osgi.wiring.* namespaces must be implemented by the Resolve Context. The Resolve Context should not return capabilities from findProviders(Requirement) unless the rules of the OSGi mandatory directive are followed.

  • singleton - Singletons are not implemented by the Resolver, the Resolve Context must not return capabilities from findProviders(Requirement) from conflicting singleton resources.

  • effective - The Resolve Context decides what requirements are effective in the isEffective(Requirement) method.

A Resolver service must support the uses constraints and any applicable rule defined in the Module Layer for the osgi.wiring.* namespaces.

The Resolver must return a delta wiring to the existing state (potentially empty) or throw an Exception. The resolution:

  • Must contain all mandatory resources Rm as provided by getMandatoryResources().

  • Must have all resources resolved.

  • Must have no wired capabilities that are declared or hosted in resources that are not resolved.

  • Should include optional resources Ro as provided by getOptionalResources().

58.3.3 Dynamic Resolving

The resolveDynamic(ResolveContext,Wiring,Requirement) method is used to resolve a dynamic requirement for an existing host wiring. For example, this method can be used to resolve dynamic package imports as specified by the DynamicImport-Package manifest header. This method may resolve additional resources in order to resolve the dynamic requirement. Dynamic resolving must return a resolution D that is a delta on the existing Wiring state X or throw a ResolutionException if the dynamic requirement cannot be resolved.

The delta must contain the host resource of the host wiring as a key. The list of wires for the host resource entry will contain a single wire that resolves the dynamic requirement to a valid capability. The delta wiring may also contain additional resources that are necessary to resolve the dynamic requirement.

To find a dynamic resolution D, the Resolver must use the Resolve Context in the same way as normal resolving except the Resolve Context is not asked for mandatory or optional resources as provided by getMandatoryResources() and getOptionalResources(). The Resolve Context is asked to find providers for the dynamic requirement as provided by findProviders(Requirement).

The Resolver assumes the following about the host wiring and the dynamic requirement:

  • The requirement uses the osgi.wiring.package namespace.

  • The requirement has a resolution directive of dynamic.

  • The requirement is hosted by the host wiring.

  • A requirement that has a cardinality directive of single is not used by an existing required wire of the host wiring.

The Resolver is not required to validate these assumptions. If these assumptions are not true then the result of the dynamic resolution is not specified.

The Resolver uses the dynamic requirement to call findProviders(Requirement) in order to find valid matching capabilities. In order for a matching capability to be considered as valid it must satisfy the following rules:

  • The capability must use the osgi.wiring.package namespace.

  • The wiring must not provide an osgi.wiring.package capability that has the same package name as the matching capability. In other words, the resolved bundle must not already export the package name.

  • The wiring must not have a required wire that wires to an osgi.wiring.package capability that has the same package name as the matching capability. In other words, the resolved bundle must not already import the package name.

The Resolver assumes the matching capabilities are valid. If invalid capabilities are returned by the Resolve Context then the result of the dynamic resolution is not specified.

At this point the dynamic resolution continues on as a normal resolution where the host wiring resource is considered a mandatory resource and the dynamic requirement is considered a mandatory requirement. The resources providing the matching capabilities to the dynamic requirement are then resolved as in a normal resolution operation.

58.3.4 Resolution Exception

If the Resolver cannot find a solution or it runs into problems then it must throw a Resolution Exception, which is a Runtime Exception.

The ResolutionException provides the getUnresolvedRequirements() method. If the resolution failed then it is possible that this was caused because it failed to find matches for certain requirements. The information in this method can be very helpful to find a solution that will work, however, there are a number of caveats.

Resolving is an NP-complete problem. For these problems there exists no algorithm that can infer a solution from the desired outcome. Therefore, the Resolver tries a potential solution and if that solution does not match the constraints it will backtrack and attempt another solution. An unavoidable aspect of such solutions is that it is impossible to pin-point a single failure point if the algorithm fails to find a solution, in general the algorithm gives up after having exhausted its search space. However, during its search it might have been very close to a solution, for example it only missed a single requirement, but its final failure missed many requirements.

The implication is that the reported missing requirements neither give a guarantee for a resolution when satisfied nor indicate that this is the smallest set of missing requirements.

Therefore, getUnresolvedRequirements() is intended for human consumption and not for automated solutions.

58.4 Security

58.4.1 Resolving

The Resolver service is a pure function that has no state. The Resolve Context maintains the state and is therefore the actor that requires most permissions. In general, it will require access to the Wiring API and Repositories.

Since the Resolver requires no external access it does not have to be a trusted service. Resolve Contexts that support security must ensure that the callbacks are executed in a privileged block.

58.4.2 Minimum Implementation Permissions

PackagePermission[org.osgi.service.resolver,IMPORT]
ServicePermission[...Resolver, REGISTER ]

58.4.3 Minimum Using Permissions

PackagePermission[org.osgi.service.repository,IMPORT]
PackagePermission[org.osgi.service.resolver,IMPORT]
PackagePermission[org.osgi.resource,IMPORT]
PackagePermission[org.osgi.framework.wiring,IMPORT]
PackagePermission[org.osgi.framework.namespaces,IMPORT]
ServicePermission[...Resolver, GET ]
... likely needs AdaptPermissions and ServicePermission[...Repository,GET]

58.5 org.osgi.service.resolver

Version 1.1

Resolver Service Package Version 1.1.

Bundles wishing to use this package must list the package in the Import-Package header of the bundle's manifest. This package has two types of users: the consumers that use the API in this package and the providers that implement the API in this package.

Example import for consumers using the API in this package:

Import-Package: org.osgi.service.resolver; version="[1.1,2.0)"

Example import for providers implementing the API in this package:

Import-Package: org.osgi.service.resolver; version="[1.1,1.2)"

58.5.1 Summary

  • HostedCapability - A capability hosted by a resource.

  • ResolutionException - Indicates failure to resolve a set of requirements.

  • ResolveContext - A resolve context provides resources, options and constraints to the potential solution of a resolve operation.

  • Resolver - A resolver service resolves the specified resources in the context supplied by the caller.

58.5.2 public interface HostedCapability
extends Capability

A capability hosted by a resource.

A HostedCapability is a Capability where the getResource() method returns a Resource that hosts this Capability instead of declaring it. This is necessary for cases where the declaring Resource of a Capability does not match the runtime state. For example, this is the case for fragments attached to a host. Most fragment declared capabilities and requirements become hosted by the host resource. Since a fragment can attach to multiple hosts, a single capability can actually be hosted multiple times.

Thread-safe

Consumers of this API must not implement this type

58.5.2.1 public Capability getDeclaredCapability()

Return the Capability hosted by the Resource.

The Capability hosted by the Resource.

58.5.2.2 public Resource getResource()

Return the Resource that hosts this Capability.

The Resource that hosts this Capability.

58.5.3 public class ResolutionException
extends Exception

Indicates failure to resolve a set of requirements.

If a resolution failure is caused by a missing mandatory dependency a resolver may include any requirements it has considered in the resolution exception. Clients may access this set of dependencies via the getUnresolvedRequirements() method.

Resolver implementations may extend this class to provide extra state information about the reason for the resolution failure.

58.5.3.1 public ResolutionException(String message, Throwable cause, Collection<Requirement> unresolvedRequirements)

The message.

The cause of this exception.

The unresolved mandatory requirements from mandatory resources or null if no unresolved requirements information is provided.

Create a ResolutionException with the specified message, cause and unresolved requirements.

58.5.3.2 public ResolutionException(String message)

The message.

Create a ResolutionException with the specified message.

58.5.3.3 public ResolutionException(Throwable cause)

The cause of this exception.

Create a ResolutionException with the specified cause.

58.5.3.4 public Collection<Requirement> getUnresolvedRequirements()

Return the unresolved requirements, if any, for this exception.

The unresolved requirements are provided for informational purposes and the specific set of unresolved requirements that are provided after a resolve failure is not defined.

A collection of the unresolved requirements for this exception. The returned collection may be empty if no unresolved requirements information is available.

58.5.4 public abstract class ResolveContext

A resolve context provides resources, options and constraints to the potential solution of a resolve operation.

Resolve Contexts:

  • Specify the mandatory and optional resources to resolve. The mandatory and optional resources must be consistent and correct. For example, they must not violate the singleton policy of the implementer.

  • Provide capabilities that the Resolver can use to satisfy requirements via the findProviders(Requirement) method

  • Constrain solutions via the getWirings() method. A wiring consists of a map of existing resources to wiring.

  • Filter requirements that are part of a resolve operation via the isEffective(Requirement).

A resolver may call the methods on the resolve context any number of times during a resolve operation using any thread. Implementors should ensure that this class is properly thread safe.

Except for insertHostedCapability(List, HostedCapability) and onCancel(Runnable), the resolve context methods must be idempotent. This means that resources must have constant capabilities and requirements and the resolve context must return a consistent set of capabilities, wires and effective requirements.

Thread-safe

58.5.4.1 public ResolveContext()

58.5.4.2 public abstract List<Capability> findProviders(Requirement requirement)

The requirement that a resolver is attempting to satisfy. Must not be null.

Find Capabilities that match the given Requirement.

The returned list contains Capability objects where the Resource must be the declared Resource of the Capability. The Resolver can then add additional HostedCapability objects with the insertHostedCapability(List, HostedCapability) method when it, for example, attaches fragments. Those HostedCapability objects will then use the host's Resource which likely differs from the declared Resource of the corresponding Capability.

The returned list is in priority order such that the Capabilities with a lower index have a preference over those with a higher index. The resolver must use the insertHostedCapability(List, HostedCapability) method to add additional Capabilities to maintain priority order. In general, this is necessary when the Resolver uses Capabilities declared in a Resource but that must originate from an attached host.

Each returned Capability must match the given Requirement. This means that the filter in the Requirement must match as well as any namespace specific directives. For example, the mandatory attributes for the osgi.wiring.package namespace.

A list of Capability objects that match the specified requirement.

58.5.4.3 public Collection<Resource> findRelatedResources(Resource resource)

The Resource that a resolver is attempting to find related resources for. Must not be null.

Find resources that are related to the given resource.

The resolver attempts to resolve related resources during the current resolve operation. Failing to resolve one of the related resources will not result in a resolution exception unless the related resource is also a mandatory resource.

The resolve context is asked to return related resources for each resource that is pulled into a resolve operation. This includes the mandatory and optional resources and each related resource returned by this method.

For example, a fragment can be considered a related resource for a host bundle. When a host is being resolved the resolve context will be asked if any related resources should be added to the resolve operation. The resolve context may decide that the potential fragments of the host should be resolved along with the host.

A collection of the resources that the resolver should attempt to resolve for this resolve context. May be empty if there are no related resources. The returned collection may be unmodifiable.

1.1

58.5.4.4 public Collection<Resource> getMandatoryResources()

Return the resources that must be resolved for this resolve context.

The default implementation returns an empty collection.

A collection of the resources that must be resolved for this resolve context. May be empty if there are no mandatory resources. The returned collection may be unmodifiable.

58.5.4.5 public Collection<Resource> getOptionalResources()

Return the resources that the resolver should attempt to resolve for this resolve context. Inability to resolve one of the specified resources will not result in a resolution exception.

The default implementation returns an empty collection.

A collection of the resources that the resolver should attempt to resolve for this resolve context. May be empty if there are no optional resources. The returned collection may be unmodifiable.

58.5.4.6 public List<Wire> getSubstitutionWires(Wiring wiring)

the wiring to get the substitution wires for. Must not be null.

Returns the subset of required wires that provide wires to capabilities which substitute capabilities of the wiring. For example, when a package name is both provided and required by the same resource. If the package requirement is resolved to a capability provided by a different wiring then the package capability is considered to be substituted.

The resolver asks the resolve context to return substitution wires for each wiring that provides a bundle namespace capability that is used to resolve one or more bundle requirements.

Note that this method searches all the package capabilities declared as provided by the resource associated with the wiring and fragment resources wired to the wiring with the host namespace. The provided package names are compared against the required package wires to determine which wires are substitution wires. Subclasses of ResolveContext should provide a more efficient implementation of this method.

A list containing a snapshot of the substitution Wires for the requirements of the wiring, or an empty list if the wiring has no substitution wires. The list contains the wires in the order they are found in the required wires of the wiring.

1.1

58.5.4.7 public abstract Map<Resource, Wiring> getWirings()

Returns the wirings for existing resolved resources.

For example, if this resolve context is for an OSGi framework, then the result would contain all the currently resolved bundles with each bundle's current wiring.

Multiple calls to this method for this resolve context must return the same result.

The wirings for existing resolved resources. The returned map is unmodifiable.

58.5.4.8 public abstract int insertHostedCapability(List<Capability> capabilities, HostedCapability hostedCapability)

The list returned from findProviders(Requirement). Must not be null.

The HostedCapability to insert in the specified list. Must not be null.

Add a HostedCapability to the list of capabilities returned from findProviders(Requirement).

This method is used by the Resolver to add Capabilities that are hosted by another Resource to the list of Capabilities returned from findProviders(Requirement). This function is necessary to allow fragments to attach to hosts, thereby changing the origin of a Capability. This method must insert the specified HostedCapability in a place that makes the list maintain the preference order. It must return the index in the list of the inserted HostedCapability.

The index in the list of the inserted HostedCapability.

58.5.4.9 public abstract boolean isEffective(Requirement requirement)

The Requirement to test. Must not be null.

Test if a given requirement should be wired in the resolve operation. If this method returns false, then the resolver should ignore this requirement during the resolve operation.

The primary use case for this is to test the effective directive on the requirement, though implementations are free to use any effective test.

true if the requirement should be considered as part of the resolve operation.

58.5.4.10 public void onCancel(Runnable callback)

the callback to execute in order to cancel the resolve operation. Must not be null.

Registers a callback with the resolve context that is associated with the currently running resolve operation. The callback can be executed in order to cancel the currently running resolve operation.

When a resolve operation begins, the resolver must call this method once and only once for the duration of the resolve operation and that call must happen before calling any other method on this resolve context. If the specified callback is executed then the resolver must cancel the currently running resolve operation and throw a ResolutionException with a cause of type CancellationException.

The callback allows a resolve context to cancel a long running resolve operation that appears to be running endlessly or at risk of running out of resources. The resolve context may then decide to give up on resolve operation or attempt to try another resolve operation with a smaller set of resources which may allow the resolve operation to complete normally.

IllegalStateException– if the resolver attempts to register more than one callback for a resolve operation

1.1

58.5.5 public interface Resolver

A resolver service resolves the specified resources in the context supplied by the caller.

Thread-safe

Consumers of this API must not implement this type

58.5.5.1 public Map<Resource, List<Wire>> resolve(ResolveContext context) throws ResolutionException

The resolve context for the resolve operation. Must not be null.

Resolve the specified resolve context and return any new resources and wires to the caller.

The resolver considers two groups of resources:

  • Mandatory - any resource in the mandatory group must be resolved. A failure to satisfy any mandatory requirement for these resources will result in throwing a ResolutionException

  • Optional - any resource in the optional group may be resolved. A failure to satisfy a mandatory requirement for a resource in this group will not fail the overall resolution but no resources or wires will be returned for that resource.

The resolve method returns the delta between the start state defined by ResolveContext.getWirings() and the end resolved state. That is, only new resources and wires are included.

The behavior of the resolver is not defined if the specified resolve context supplies inconsistent information.

The new resources and wires required to satisfy the specified resolve context. The returned map is the property of the caller and can be modified by the caller.

ResolutionException– If the resolution cannot be satisfied.

58.5.5.2 public Map<Resource, List<Wire>> resolveDynamic(ResolveContext context, Wiring hostWiring, Requirement dynamicRequirement) throws ResolutionException

The resolve context for the resolve operation. Must not be null.

The wiring with the dynamic requirement. Must not be null.

The dynamic requirement. Must not be null.

Resolves a given requirement dynamically for the given host wiring using the given resolve context and return any new resources and wires to the caller.

The requirement must be a requirement of the wiring and must use the package namespace with a resolution of type dynamic.

The resolve context is not asked for mandatory resources or for optional resources. The resolve context is asked to find providers for the given requirement. The matching package capabilities returned by the resolve context must not have a osgi.wiring.package attribute equal to a package capability already wired to by the wiring or equal a package capability provided by the wiring. The resolve context may be requested to find providers for other requirements in order to resolve the resources that provide the matching capabilities to the given requirement.

If the requirement cardinality is not multiple then no new wire must be created if the wires of the wiring already contain a wire that uses the requirement

This operation may resolve additional resources in order to resolve the dynamic requirement. The returned map will contain entries for each resource that got resolved in addition to the specified wiring resource. The wire list for the wiring resource will only contain one wire which is for the dynamic requirement.

The new resources and wires required to satisfy the specified dynamic requirement. The returned map is the property of the caller and can be modified by the caller. If no new wires were created then a ResolutionException is thrown.

ResolutionException– if the dynamic requirement cannot be resolved

58.6 References

[1]Repository Service SpecificationOSGi Compendium, Chapter 132 Repository Service Specification

58.7 Changes

  • Added new methods: findRelatedResources, onCancel, getSubstitutionWires, and resolveDynamic.