100 Remote Services

Version 1.1

The OSGi framework provides a local service registry for bundles to communicate through service objects, where a service is an object that one bundle registers and another bundle gets. A distribution provider can use this loose coupling between bundles to export a registered service by creating an endpoint. Vice versa, the distribution provider can create a proxy that accesses an endpoint and then registers this proxy as an imported service. A Framework can contain multiple distribution providers simultaneously, each independently importing and exporting services.

An endpoint is a communications access mechanisms to a service in another framework, a (web) service, another process, or a queue or topic destination, etc., requiring some protocol for communications. The constellation of the mapping between services and endpoints as well as their communication characteristics is called the topology. A common case for distribution providers is to be present on multiple frameworks importing and exporting services; effectively distributing the service registry.

The local architecture for remote services is depicted in Figure 100.1 on page .

Figure 100.1 Architecture

Architecture

Local services imply in-VM call semantics. Many of these semantics cannot be supported over a communications connection, or require special configuration of the communications connection. It is therefore necessary to define a mechanism for bundles to convey their assumptions and requirements to the distribution provider. This chapter defines a number of service properties that a distribution provider can use to establish a topology while adhering to the given constraints.

100.1 The Fallacies

General abstractions for distributed systems have been tried before and often failed. Well known are the fallacies described in [1] The Fallacies of Distributed Computing Explained:

  • The network is reliable

  • Latency is zero

  • Bandwidth is infinite

  • The network is secure

  • Topology doesn't change

  • There is one administrator

  • Transport cost is zero

  • The network is homogeneous

Most fallacies represent non-functional trade-offs that should be considered by administrators, their decisions can then be reflected in the topology. For example, in certain cases limited bandwidth is acceptable and the latency in a datacenter is near zero. However, the reliability fallacy is the hardest because it intrudes into the application code. If a communication channel is lost, the application code needs to take specific actions to recover from this failure.

This reliability aspect is also addressed with OSGi services because services are dynamic. Failures in the communications layer can be mapped to the unregistration of the imported service. OSGi bundles are already well aware of these dynamics, and a number of programming models have been developed to minimize the complexity of writing these dynamic applications.

100.2 Remote Service Properties

This section introduces a number of properties that participating bundles can use to convey information to the distribution provider according to this Remote Service specification.

The following table lists the properties that must be listed by a distribution provider.

Table 100.1 Remote Service Properties registered by the Distribution Provider

Service Property Name Type Description
remote.configs.supported String+

Registered by the distribution provider on one of its services to indicate the supported configuration types. See Configuration Types and Dependencies.

remote.intents.supported String+

Registered by the distribution provider on one of its services to indicate the vocabulary of implemented intents. See Dependencies.

service.imported *

Must be set by a distribution provider to any value when it registers the endpoint proxy as an imported service. A bundle can use this property to filter out imported services.

service.imported.configs String+

The configuration information used to import this service, as described in service.exported.configs. Any associated properties for this configuration types must be properly mapped to the importing system. For example, a URL in these properties must point to a valid resource when used in the importing framework.

If multiple configuration types are listed in this property, then they must be synonyms for exactly the same remote endpoint that is used to export this service.

service.intents String+

A distribution provider must use this property to convey the combined intents of:

  • The exporting service, and

  • The intents that the exporting distribution provider adds.

  • The intents that the importing distribution provider adds.


The properties for bundles providing services to be exported or require services to be imported are listed alphabetically in the following table. The scenarios that these properties are used in are discussed in later sections.

Table 100.2 Remote Service Properties registered by Exporting bundles

Service Property Name Type Description
service.exported.configs String+

A list of configuration types that should be used to export the service. Each configuration type represents the configuration parameters for one or more Endpoints. A distribution provider should create endpoints for each configuration type that it supports. See Configuration Types for more details. If this property is not set or empty a distribution provider is free to choose a default configuration type for the service.

service.exported.intents String+

A list of intents that the distribution provider must implement to distribute the service. Intents listed in this property are reserved for intents that are critical for the code to function correctly, for example, ordering of messages. These intents should not be configurable. For more information about intents, see Intents. This property is optional.

service.exported.intents.extra String+

This property is merged with the service.exported.intents property before the distribution provider interprets the listed intents; it has therefore the same semantics but the property should be configurable so the administrator can choose the intents based on the topology. Bundles should therefore make this property configurable, for example through the Configuration Admin service. See Intents. This property is optional. If absent or empty no specific intents are required.

service.exported.interfaces String+

Setting this property marks this service for export. It defines the interfaces under which this service can be exported. This list must be a subset of the types listed in the objectClass service property. The single value of an asterisk ('*' \u002A) indicates all interfaces in the registration's objectClass property and ignore the classes. It is strongly recommended to only export interfaces and not concrete classes due to the complexity of creating proxies for some type of concrete classes. See Registering a Service for Export.

service.intents String+

A list of intents that this service implements. A distribution provider must use this property to convey the combined intents of:

  • The exporting service, and

  • The intents that the exporting distribution provider adds.

  • The intents that the importing distribution provider adds.

To export a service, a distribution provider must expand any qualified intents to include those supported by the endpoint. This can be a subset of all known qualified intents. See Intents. This property is optional for registering bundles.

service.pid String+

Services that are exported should have a service.pid property. The service.pid (PID) is a unique persistent identity for the service, the PID is defined in Persistent Identifier (PID) of OSGi Core Release 8. This property enables a distribution provider to associate persistent proprietary data with a service registration.


The properties and their treatment by the distribution provider is depicted in Figure 100.2.

Figure 100.2 Distribution Service Properties

Distribution Service Properties

100.2.1 Registering a Service for Export

A distribution provider should create one or more endpoints for an exported service when the following conditions are met:

  • The service has the service property service.exported.interfaces set.

  • All intents listed in service.exported.intents, service.exported.intents.extra and service.intents are part of the distributed provider's vocabulary

  • None of the intents are mutually exclusive.

  • The distribution provider can use the configuration types in service.exported.configs to create one or more endpoints.

The endpoint must at least implement all the intents that are listed in the service.exported.intents and service.exported.intents.extra properties.

The configuration types listed in the service.exported.configs can contain alternatives and/or synonyms. Alternatives describe different endpoints for the same service while a synonym describes a different configuration type for the same endpoint.

A distribution provider should create endpoints for each of the configuration types it supports; these configuration types should be alternatives. Synonyms are allowed.

If no configuration types are recognized, the distribution provider should create an endpoint with a default configuration type except when one of the listed configuration types is <<nodefault>>.

For more information about the configuration types, see further Configuration Types.

100.2.2 Getting an Imported Service

An imported service must be a normal service, there are therefore no special rules for getting it. An imported service has a number of additional properties that must be set by the distribution provider.

If the endpoint for an exported service is imported as an OSGi service in another framework, then the following properties must be treated as special.

  • service.imported - Must be set to some value.

  • service.intents - This must be the combination of the following:

    • The service.intents property on the exported service

    • The service.exported.intents and service.exported.intents.extra properties on the exported service

    • Any additional intents implemented by the distribution providers on both sides.

  • service.imported.configs - Contains the configuration types that can be used to import this service. The types listed in this property must be synonymous, that is, they must refer to exactly the same endpoint that is exporting the service. See Configuration Types.

  • service.exported.* - Properties starting with service.exported. must not be set on the imported service.

  • service.exported.interfaces - This property must not be set, its content is reflected in the objectClass property.

All other public service properties (not starting with a full stop ('.' \u002E)) must be listed on the imported service if they use the basic service property types. If the service property cannot be communicated because, for example, it uses a type that can not be marshaled by the distribution provider then the distribution provider must ignore this property.

The service.imported property indicates that a service is an imported service. If this service property is set to any value, then the imported service is a proxy for an endpoint. If a bundle wants to filter out imported services, then it can add the following filter:

(&(!(service.imported=*)) <previousfilter>)

Distribution providers can also use the Service Hook Service Specification of OSGi Core Release 8 to hide services from specific bundles.

100.2.3 On Demand Import

The Service Hooks Service Specification of OSGi Core Release 8, allows a distribution provider to detect when a bundle is listening for specific services. Bundles can request imported services with specific intents by building an appropriate filter. The distribution provider can use this information to import a service on demand.

The following example creates a Service Tracker that is interested in an imported service.

Filter f = context.createFilter(
        "(&(objectClasss=com.acme.Foo)"
    +   "(service.intents=confidentiality))"
);
ServiceTracker tracker =
    new ServiceTracker(context, f, null );
tracker.open();

Such a Service Tracker will inform the Listener Hook and will give it the filter expression. If the distribution provider has registered such a hook, it will be informed about the need for an imported com.acme.Foo service that has a confidentiality intent. It can then use some proprietary means to find a service to import that matches the given object class and intent.

How the distribution provider finds an appropriate endpoint is out of scope for this specification.

100.3 Intents

An intent is a name for an abstract distribution capability. An intent can be implemented by a service; this can then be reflected in the service.intents property. An intent can also constrain the possible communication mechanisms that a distribution provider can choose to distribute a service. This is reflected in the service.exported.intents and service.exported.intents.extra properties.

The purpose of the intents is to have a vocabulary that is shared between distribution aware bundles and the distribution provider. This vocabulary allows the bundles to express constraints on the export of their services as well as providing information on what intents are implemented by a service.

Intents have the following syntax

intent  ::= token ( '.' token )?

Qualified intents use a full stop ('.' \u002E) to separate the intent from the qualifier. A qualifier provides additional details, however, it implies its prefix. For example:

confidentiality.message

This example, can be expanded into confidentiality and confidentiality.message. Qualified intents can be used to provide additional details how an intent is achieved. However, a Distribution Provider must expand any qualified intents to include those supported by the endpoint. This can be a subset of all known qualified intents.

The concept of intents is derived from the [3] SCA Policy Framework specification. When designing a vocabulary for a distribution provider it is recommended to closely follow the vocabulary of intents defined in the SCA Policy Framework.

100.3.1 Basic Remote Services: osgi.basic

Remote Services implementations have a large amount of freedom. For example, they may use any mechanism that they choose to transmit data between the caller of the remote service and the provider of the service. This freedom means that there can be a large variation in the behaviors supported by different Remote Services implementations.

The purpose of the osgi.basic intent is to provide a common set of rules that can be relied upon when exporting a simple remote service. This includes rules about the service interface, including supported parameter and return types, as well as a means of configuring a timeout for remote invocations.

100.3.1.1 Minimum Supported Service Signature

Remote Services implementations which offer the osgi.basic intent must support remote services which advertise a single Java interface containing zero or more methods.

The following types must be supported as declared parameters or returns from methods on the remote service:

  • Primitive values

  • The OSGi scalar types, OSGi Version objects, Java enums, and types which conform to the OSGi DTO rules as described in the OSGi core specification. In the rest of this section these will be known as the basic types.

  • Arrays of primitive values or the basic types

  • Lists, Collections or Iterables of the basic types, however the implementation of the collection may not be preserved in transit. For example a LinkedList may be converted to an ArrayList.

  • Sets of the OSGi basic types where equals is used to determine identity. SortedSet is not required to be supported due to the difficulties associated with serializing comparators. The implementation of the set may not be preserved in transit. For example a LinkedHashSet may be converted to a HashSet.

  • Maps where the keys and values are the OSGi basic types, and equals is used to determine identity for the keys. SortedMap is not required to be supported due to the difficulties associated with serializing comparators. The implementation of the map may not be preserved in transit. For example a LinkedHashMap may be converted to a HashMap.

  • Methods with no arguments, and methods with a void return

100.3.1.2 Remote Invocation Timeout

The implementation of a Remote Services provider is entirely opaque. In many cases there will be no feedback mechanism if the remote call hangs, or if the remote node fails. The local client must therefore decide at what point to fail after a certain amount of time has elapsed.

A single Remote Services implementation must be able to handle a wide variety of different remote service invocations across many services, therefore it is difficult to identify a sensible timeout for the remote service invocation. Some calls may be quick, and so a ten second timeout is desirable for rapid failure detection, other calls may be long-running, and a two minute timeout too short. The remote service must therefore be able to declare its own timeout.

To declare a timeout the remoteable service may provide a service property osgi.basic.timeout which provides a timeout value in milliseconds. The value may be declared as a String or as a Number, which will be converted into a Long. The timeout value is used to limit the maximum time for which a remote service client will be blocked waiting for a response. The same timeout value applies to all methods on the service. In the event that the invocation reaches the timeout value the client must fail the method call with a ServiceException with its type set to REMOTE.

100.3.2 Asynchronous Remote Services: osgi.async

Some service invocations operate asynchronously, returning quickly and continuing to process in the background. For void methods with no completion notifications this is simple to achieve remotely, but more useful scenarios are difficult to support without using higher-level abstractions to represent the eventual result.

The purpose of the osgi.async intent is to provide a common set of rules that can be relied upon for remote services which return types representing an asynchronously executing method.

The osgi.async intent makes no guarantees about the service interface(s) or method parameters supported by the remote services implementation. It is therefore recommended that it be used in conjunction with another intent, such as the osgi.basic intent.

100.3.2.1 Supported Return Types

Asynchronous returns are implemented using a holder type. The holder represents the state of the asynchronous execution, and can be queried for its completion state. When the execution is complete the holder can be queried for the result of the execution, or for its failure.

The following holder types must be supported as return types from methods on the remote service:

  • org.osgi.util.promise.Promise

  • java.util.concurrent.Future

  • java.util.concurrent.CompletionStage

  • java.util.concurrent.CompletableFuture

The full set of supported types for the eventual return value encapsulated by the holder object are not defined by the osgi.async intent. Instead the full set of supported types can be inferred from the other supported intents supported by the Remote Services implementation. For example the osgi.basic intent would ensure support for a return value of Promise<List<String>>

100.3.2.2 Asynchronous Failures

If an asynchronous remote execution fails then the holder type must be failed with the same exception that would have been thrown in a synchronous call.

The reason for the failure may be as a result of a failure in communications, a timeout, or because the remote invocation resulted in an exception

100.3.3 Confidential Remote Services: osgi.confidential

The osgi.confidential intent can be used to state that the remote service communications must only be readable by the intended recipient, for example, through the use of TLS-based transport encryption.

If a Remote Services implementation does not support confidential communications, or is not configured as such, it must not expose the service remotely.

100.3.4 Private Remote Services: osgi.private

In many deployment scenarios, including cloud, embedded or IoT deployments, hosts may be accessible via a public network and via a private network. In such cases hosts will have multiple IP addresses to separate public network access from private network access. Private IP addresses normally in one of the following blocks: 10.0.0.0/8, 172.16.0.0/12 or 192.168.0.0/16.

In many cases it is desirable to expose remote services only on the private network so that these services cannot be accessed from the outside world. This is especially useful if this service is used as a microservice within a larger application. The osgi.private intent can be specified for this purpose.

If the osgi.private intent is required on the remote service, it will only be exposed as a remote service on a private network on the host. If the host does not support a private IP address or if the Remote Services implementation does not have the information to decide whether a host IP is private, the service should not be exposed.

100.4 General Usage

100.4.1 Call by Value

Normal service semantics are call-by-reference. An object passed as an argument in a service call is a direct reference to that object. Any changes to this object will be shared on both sides of the service registry.

Distributed services are different. Arguments are normally passed by value, which means that a copy is sent to the remote system, changes to this value are not reflected in the originating framework. When using distributed services, call-by-value should always be assumed by all participants in the distribution chain.

100.4.2 Data Fencing

Services are syntactically defined by their Java interfaces. When exposing a service over a remote protocol, typically such an interface is mapped to a protocol-specific interface definition. For example, in CORBA the Java interfaces would be converted to a corresponding IDL definition. This mapping does not always result in a complete solution.

Therefore, for many practical distributed applications it will be necessary to constrain the possible usage of data types in service interfaces. A distribution provider must at least support interfaces (not classes) that only use the basic types as defined for the service properties. These are the primitive types and their wrappers as well as arrays and collections. See Filter Syntax of OSGi Core Release 8 for a list of service property types.

Distribution providers will in general provide a richer set of types that can be distributed.

100.4.3 Remote Services Life Cycle

A distributed service must closely track any modifications on the corresponding service registration. If service properties are modified, these modifications should be propagated to the distributed service and associated service proxies. If the exported service is unregistered, the endpoint must be withdrawn as soon as possible and any imported service proxies unregistered.

100.4.4 Runtime

An imported service is just like any other service and can be used as such. However, certain non-functional characteristics of this service can differ significantly from what is normal for an in-VM object call. Many of these characteristics can be mapped to the normal service operations. That is, if the connection fails in any way, the service can be unregistered. According to the standard OSGi contract, this means that the users of that service must perform the appropriate cleanup to prevent stale references.

100.4.5 Exceptions

It is impossible to guarantee that a service is not used when it is no longer valid. Even with the synchronous callbacks from the Service Listeners, there is always a finite window where a service can be used while the underlying implementation has failed. In a distributed environment, this window can actually be quite large for an imported service.

Such failure situations must be exposed to the application code that uses a failing imported service. In these occasions, the distribution provider must notify the application by throwing a Service Exception, or subclass thereof, with the reason REMOTE. The Service Exception is a Runtime Exception, it can be handled higher up in the call chain. The cause of this Service Exception must be the Exception that caused the problem.

A distribution provider should log any problems with the communications layer to the Log Service, if available.

100.5 Configuration Types

An exported service can have a service.exported.configs service property. This property lists configuration types for endpoints that are provided for this service. Each type provides a specification that defines how the configuration data for one or more endpoints is provided. For example, a hypothetical configuration type could use a service property to hold a URL for the RMI naming registry.

Configuration types that are not defined by the OSGi Working Group should use a name that follows the reverse capabilities domain name scheme defined in [4] Java Language Specification for Java packages. For example, com.acme.wsdl would be the proprietary way for the ACME company to specify a WSDL configuration type.

100.5.1 Configuration Type Properties

The service.exported.configs and service.imported.configs use the configuration types in very different ways. That is, the service.imported.configs property is not a copy of the service.exported.configs as the name might seem to imply.

An exporting service can list its desired configuration types in the service.exported.configs property. This property is potentially seen and interpreted by multiple distribution providers. Each of these providers can independently create endpoints from the configuration types. In principle, the service.exported.configs lists alternatives for a single distribution provider and can list synonyms to support alternative distribution providers. If only one of the synonyms is useful, there is an implicit assumption that when the service is exported, only one of the synonyms should be supported by the installed distribution providers. If it is detected that this assumption is violated, then an error should be logged and the conflicting configuration is further ignored.

The interplay of synonyms and alternatives is depicted in Table 100.3. In this table, the first columns on the left list different combinations of the configuration types in the service.exported.configs property. The next two columns list two distribution providers that each support an overlapping set of configuration types. The x's in this table indicate if a configuration type or distribution provider is active in a line. The description then outlines the issues, if any. It is assumed in this table that hypothetical configuration types net.rmi and com.rmix map to an identical endpoint, just like net.soap and net.soapx.

Table 100.3 Synonyms and Alternatives in Exported Configurations

service.exported. configs Distribution Provider A Distribution Provider B Description
net.rmi com.rmix net.soap com.soapx <<no default>>

Supports:

net.rmi

com.rmix

com.soapx

Supports:

net.rmi

net.soap

 

x

x

x

OK, A will create an endpoint for the RMI and SOAP alternatives.

x

x

x

Configuration error. There is a clash for net.rmi because A and B can both create an endpoint for the same configuration. It is likely that one will fail.

x

x

x

OK, exported on com.soapx by A, the net.soap is ignored.

x

x

x

x

Synonym error because A and B export to same SOAP endpoint, it is likely that one will fail.

x

x

x

x

OK, two alternative endpoints over RMI (by A) and SOAP (by B) are created. This is a typical use case.

x

x

x

OK. Synonyms are used to allow frameworks that have either A or B installed. In this case A exports over SOAP.

x

x

x

OK. Synonyms are used to allow frameworks that have either A or B installed. In this case B exports.

x

OK. A creates an endpoint with default configuration type.

x

x

OK. Both A and B each create an endpoint with their default configuration type.

x

x

OK. No endpoint is created.

x

x

x

Provider B does not recognize the configuration types it should therefore use a default configuration type.


To summarize, the following rules apply for a single distribution provider:

  • Only configuration types that are supported by this distribution provider must be used. All other configuration types must be ignored.

  • All of the supported configuration types must be alternatives, that is, they must map to different endpoints. Synonyms for the same distribution provider should be logged as errors.

  • If a configuration type results in an endpoint that is already in use, then an error should be logged. It is likely then that another distribution provider already had created that endpoint.

An export of a service can therefore result in multiple endpoints being created. For example, a service can be exported over RMI as well as SOAP. Creating an endpoint can fail, in that case the distribution provider must log this information in the Log Service, if available, and not export the service to that endpoint. Such a failure can, for example, occur when two configuration types are synonym and multiple distribution providers are installed that supporting this type.

On the importing side, the service.imported.configs property lists configuration types that must refer to the same endpoint. That is, it can list alternative configuration types for this endpoint but all configuration types must result in the same endpoint.

For example, there are two distribution providers installed at the exporting and importing frameworks. Distribution provider A supports the hypothetical configuration type net.rmi and net.soap. Distribution provider B supports the hypothetical configuration type net.smart. A service is registered that list all three of those configuration types.

Distribution provider A will create two endpoints, one for RMI and one for SOAP. Distribution provider B will create one endpoint for the smart protocol. The distribution provider A knows how to create the configuration data for the com.acme.rmi configuration type as well and can therefore create a synonymous description of the endpoint in that configuration type. It will therefore set the imported configuration type for the RMI endpoint to:

service.imported.configs = net.rmi, com.acme.rmi
net.rmi.url = rmi://172.25.25.109:1099/service-id/24
com.acme.rmi.address = 172.25.25.109
com.acme.rmi.port = 1099
com.acme.rmi.path = service-id/24

Figure 100.3 Relation between imported and exported configuration types

Relation between imported and exported configuration types

100.5.2 Dependencies

A bundle that uses a configuration type has an implicit dependency on the distribution provider. To make this dependency explicit, the distribution provider must register a service with the following properties:

  • remote.intents.supported - (String+) The vocabulary of the given distribution provider.

  • remote.configs.supported - (String+) The configuration types that are implemented by the distribution provider.

A bundle that depends on the availability of specific intents or configuration types can create a service dependency on an anonymous service with the given properties. The following filter is an example of depending on a hypothetical net.rmi configuration type:

(remote.configs.supported=net.rmi)

100.6 Security

The distribution provider will be required to invoke methods on any exported service. This implies that it must have the combined set of permissions of all methods it can call. It also implies that the distribution provider is responsible for ensuring that a bundle that calls an imported service is not granted additional permissions through the fact that the distribution provider will call the exported service, not the original invoker.

The actual mechanism to ensure that bundles can get additional permissions through the distribution is out of scope for this specification. However, distribution providers should provide mechanisms to limit the set of available permissions for a remote invocation, preferably on a small granularity basis.

One possible means is to use the getAccessControlContext method on the Conditional Permission Admin service to get an Access Control Context that is used in a doPrivileged block where the invocation takes place. The getAccessControlContext method takes a list of signers which could represent the remote bundles that cause an invocation. How these are authenticated is up to the distribution provider.

A distribution provider is a potential attack point for intruders. Great care should be taken to properly setup the permissions or topology in an environment that requires security.

100.6.1 Limiting Exports and Imports

Service registration and getting services is controlled through the ServicePermission class. This permission supports a filter based constructor that can assert service properties. This facility can be used to limit bundles from being able to register exported services or get imported services if they are combined with Conditional Permission Admin's ALLOW facility. The following example shows how all bundles except from www.acme.com are denied the registration and getting of distributed services.

DENY {
   [...BundleLocationCondition("http://www.acme.com/*" "!")]
   (...ServicePermission "(service.imported=*)" "GET" )
   (...ServicePermission "(service.exported.interfaces=*)"
                                "REGISTER" )
}

100.7 References

[2]Service Component Architecture (SCA)https://www.osoa.org/

[3]SCA Policy Framework specificationhttps://www.osoa.org/

[4]Java Language Specificationhttps://docs.oracle.com/javase/specs/