Skip to content

Commit

Permalink
Very early draft of a ServiceFactory handling
Browse files Browse the repository at this point in the history
  • Loading branch information
tcalmant committed Mar 25, 2017
1 parent 70845d4 commit 0e9d43a
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 8 deletions.
19 changes: 14 additions & 5 deletions pelix/framework.py
Expand Up @@ -896,14 +896,18 @@ def install_visiting(self, path, visitor, prefix=None):

return bundles, failed

def register_service(self, bundle, clazz, service, properties, send_event):
def register_service(self, bundle, clazz, service, properties, send_event,
factory=False, prototype=False):
"""
Registers a service and calls the listeners
:param bundle: The bundle registering the service
:param clazz: Name(s) of the interface(s) implemented by service
:param properties: Service properties
:param send_event: If not, doesn't trigger a service registered event
:param factory: If True, the given service is a service factory
:param prototype: If True, the given service is a prototype service
factory (the factory argument is considered True)
:return: A ServiceRegistration object
:raise BundleException: An error occurred while registering the service
"""
Expand Down Expand Up @@ -938,8 +942,8 @@ def register_service(self, bundle, clazz, service, properties, send_event):
classes.append(svc_clazz)

# Make the service registration
registration = self._registry.register(bundle, classes, properties,
service)
registration = self._registry.register(
bundle, classes, properties, service, factory, prototype)

# Update the bundle registration information
bundle._registered_service(registration)
Expand Down Expand Up @@ -1405,19 +1409,24 @@ def install_visiting(self, path, visitor):
"""
return self.__framework.install_visiting(path, visitor)

def register_service(self, clazz, service, properties, send_event=True):
def register_service(self, clazz, service, properties, send_event=True,
factory=False, prototype=False):
"""
Registers a service
:param clazz: Class or Classes (list) implemented by this service
:param service: The service instance
:param properties: The services properties (dictionary)
:param send_event: If not, doesn't trigger a service registered event
:param factory: If True, the given service is a service factory
:param prototype: If True, the given service is a prototype service
factory (the factory argument is considered True)
:return: A ServiceRegistration object
:raise BundleException: An error occurred while registering the service
"""
return self.__framework.register_service(
self.__bundle, clazz, service, properties, send_event)
self.__bundle, clazz, service, properties, send_event,
factory, prototype)

def remove_bundle_listener(self, listener):
"""
Expand Down
79 changes: 76 additions & 3 deletions pelix/internals/registry.py
Expand Up @@ -32,7 +32,8 @@

# Pelix beans
from pelix.constants import OBJECTCLASS, SERVICE_ID, SERVICE_RANKING, \
SERVICE_BUNDLEID, SERVICE_SCOPE, SCOPE_SINGLETON, BundleException
SERVICE_BUNDLEID, SERVICE_SCOPE, SCOPE_SINGLETON, SCOPE_BUNDLE, \
SCOPE_PROTOTYPE, BundleException
from pelix.internals.events import ServiceEvent

# Pelix utility modules
Expand Down Expand Up @@ -220,6 +221,23 @@ def get_property_keys(self):
with self._props_lock:
return tuple(self.__properties.keys())

def is_factory(self):
"""
Returns True if this reference points to a service factory
:return: True if the service provides from a factory
"""
return self.__properties[SERVICE_SCOPE] in \
(SCOPE_BUNDLE, SCOPE_PROTOTYPE)

def is_prototype(self):
"""
Returns True if this reference points to a prototype service factory
:return: True if the service provides from a prototype factory
"""
return self.__properties[SERVICE_SCOPE] == SCOPE_PROTOTYPE

def unused_by(self, bundle):
"""
Indicates that this reference is not being used anymore by the given
Expand Down Expand Up @@ -686,6 +704,9 @@ def __init__(self, framework, logger=None):
# Service reference -> Service instance
self.__svc_registry = {}

# Service reference -> (Service factory, Service Registration)
self.__svc_factories = {}

# Specification -> Service references[] (always sorted)
self.__svc_specs = {}

Expand All @@ -707,19 +728,24 @@ def clear(self):
"""
with self.__svc_lock:
self.__svc_registry.clear()
self.__svc_factories.clear()
self.__svc_specs.clear()
self.__bundle_svc.clear()
self.__bundle_imports.clear()
self.__pending_services.clear()

def register(self, bundle, classes, properties, svc_instance):
def register(self, bundle, classes, properties, svc_instance,
factory, prototype):
"""
Registers a service.
:param bundle: The bundle that registers the service
:param classes: The classes implemented by the service
:param properties: The properties associated to the service
:param svc_instance: The instance of the service
:param factory: If True, the given service is a service factory
:param prototype: If True, the given service is a prototype service
factory (the factory argument is considered True)
:return: The ServiceRegistration object
"""
with self.__svc_lock:
Expand All @@ -729,7 +755,14 @@ def register(self, bundle, classes, properties, svc_instance):
properties[OBJECTCLASS] = classes
properties[SERVICE_ID] = service_id
properties[SERVICE_BUNDLEID] = bundle.get_bundle_id()
properties[SERVICE_SCOPE] = SCOPE_SINGLETON

# Compute service scope
if prototype:
properties[SERVICE_SCOPE] = SCOPE_PROTOTYPE
elif factory:
properties[SERVICE_SCOPE] = SCOPE_BUNDLE
else:
properties[SERVICE_SCOPE] = SCOPE_SINGLETON

# Force to have a valid service ranking
try:
Expand All @@ -745,6 +778,10 @@ def register(self, bundle, classes, properties, svc_instance):
self.__framework, svc_ref, properties, self.__sort_registry)

# Store service information
if prototype or factory:
self.__svc_factories[svc_ref] = (svc_instance, svc_registration)

# Also store factories, as they must appear like any other service
self.__svc_registry[svc_ref] = svc_instance

for spec in classes:
Expand Down Expand Up @@ -807,6 +844,11 @@ def unregister(self, svc_ref):
# Get the service instance
service = self.__svc_registry.pop(svc_ref)

# Remove the service factory
if svc_ref.is_factory():
# TODO: notify the factory ?
del self.__svc_factories[svc_ref]

for spec in svc_ref.get_property(OBJECTCLASS):
spec_services = self.__svc_specs[spec]
# Use bisect to remove the reference (faster)
Expand Down Expand Up @@ -954,6 +996,9 @@ def get_service(self, bundle, reference):
:return: The requested service
:raise BundleException: The service could not be found
"""
if reference.is_factory():
return self.__get_service_from_factory(bundle, reference)

with self.__svc_lock:
# Be sure to have the instance
try:
Expand All @@ -969,6 +1014,28 @@ def get_service(self, bundle, reference):
raise BundleException("Service not found (reference: {0})"
.format(reference))

def __get_service_from_factory(self, bundle, reference):
"""
Returns a service instance from a service factory or a prototype
service factory
:param bundle: The bundle requiring the service
:param reference: A reference pointing to a factory
:return: The requested service
:raise BundleException: The service could not be found
"""
with self.__svc_lock:
try:
# TODO: increase usage counter
# TODO: only one call per factory per bundle
# TODO; handle prototypes
factory, svc_reg = self.__svc_factories[reference]
return factory.get_service(bundle, svc_reg)
except KeyError:
# Not found
raise BundleException("Service not found (reference: {0})"
.format(reference))

def unget_service(self, bundle, reference):
"""
Removes the usage of a service by a bundle
Expand All @@ -988,6 +1055,12 @@ def unget_service(self, bundle, reference):
# Unknown reference
return False
else:
# Service factory
# TODO: better handling
if reference.is_factory():
factory, svc_reg = self.__svc_factories[reference]
factory.unget_service(bundle, svc_reg)

# Clean up
if not imports:
del self.__bundle_imports[bundle]
Expand Down
68 changes: 68 additions & 0 deletions tests/framework/service_factory_bundle.py
@@ -0,0 +1,68 @@
#!/usr/bin/env python
# -- Content-Encoding: UTF-8 --
"""
Simple bundle registering a service
:author: Thomas Calmant
"""

__version__ = (1, 0, 0)

SVC = "greetings"

from pelix.constants import BundleActivator


class Service:
"""
Service per bundle
"""
def __init__(self, bundle_id):
self.__id = bundle_id

def show(self):
return self.__id


class ServiceFactoryTest:
"""
Simple test service
"""
def get_service(self, bundle, registration):
"""
Provide a new service
"""
return Service(bundle.get_bundle_id())


@BundleActivator
class ActivatorService:
"""
Test activator
"""
def __init__(self):
"""
Constructor
"""
self.context = None
self.factory = None
self.reg = None

def start(self, context):
"""
Bundle started
"""
self.context = context

# Register the service
self.factory = ServiceFactoryTest()
self.reg = context.register_service(
SVC, self.factory, {"test": True, "answer": 0},
factory=True)

def stop(self, context):
"""
Bundle stopped
"""
self.reg.unregister()
self.reg = None
79 changes: 79 additions & 0 deletions tests/framework/test_service_factories.py
@@ -0,0 +1,79 @@
#!/usr/bin/env python
# -- Content-Encoding: UTF-8 --
"""
Pelix framework test module. Tests the framework, bundles handling, service
handling and events.
:author: Thomas Calmant
"""

# Pelix
from pelix.framework import FrameworkFactory, Bundle, BundleException, \
BundleContext, ServiceReference
import pelix.constants

# Standard library
try:
import unittest2 as unittest
except ImportError:
import unittest

# ------------------------------------------------------------------------------

__version__ = "1.0.0"

# ------------------------------------------------------------------------------


class ServicesTest(unittest.TestCase):
"""
Pelix services registry tests
"""
def setUp(self):
"""
Called before each test. Initiates a framework and loads the current
module as the first bundle
"""
self.test_bundle_name = "tests.framework.service_factory_bundle"

self.framework = FrameworkFactory.get_framework()
self.framework.start()

def tearDown(self):
"""
Called after each test
"""
self.framework.stop()
FrameworkFactory.delete_framework()

def test_factory(self):
"""
Tests the basic behaviour of a service factory
"""
context = self.framework.get_bundle_context()
bundle = context.install_bundle(self.test_bundle_name)
module = bundle.get_module()
bundle.start()

svc_ref = context.get_service_reference(module.SVC)
svc = context.get_service(svc_ref)
self.assertEqual(svc.show(), 0)
context.unget_service(svc_ref)

svc = context.get_service(svc_ref)
self.assertEqual(svc.show(), 0)
context.unget_service(svc_ref)

bc = bundle.get_bundle_context()
svc = bc.get_service(svc_ref)
self.assertEqual(svc.show(), bundle.get_bundle_id())
bc.unget_service(svc_ref)

# ------------------------------------------------------------------------------

if __name__ == "__main__":
# Set logging level
import logging
logging.basicConfig(level=logging.DEBUG)

unittest.main()

0 comments on commit 0e9d43a

Please sign in to comment.