The Educational Story of the Service Objects

I am working on a project where someone expressed a great desire to use the OSGi PROTOTYPE scope. This PROTOTYPE feature was added to OSGi in my absence and then I admit grudgingly that such absence rarely makes my heart grow fonder. A customer wanted PROTOTYPE services so I did use them. I now have some experience and I think there are definitely some issues around prototype scoped services.

The basic idea of a PROTOTYPE service is that instead of getting the real service from the Bundle Context, you get a Service Objects (note the last s) that you can use to instantiate as many of those services as you want. On the service side, you need to register a Prototype Service Factory to get a callback for every instantiation and disposal.

Obviously, this API for the PROTOTYPE feature suffers the same problem as much of the core OSGi API. Quite powerful but horribly complex and abstract.

Fortunately, like the normal API, Declarative Services (DS) and its adoring annotations come to the rescue.

@Component(scope = PROTOTYPE, service = Stamp.class )
public class Stamp {
   final static AtomicLong counter = new AtomicLong(0);
   final long count = counter.incrementAndGet();
}

The alternative to the PROTOTYPE scope was to make the life cycle explicit. I have some vague recollections in the pre-historic OSGi time of the genuine service factories as I coined them before they got renamed to parameterized services. I remember I concluded in the end just creating a service that managed the life cycle of the instance was easier to understand because it remained in the typed world of Java where we all feel safe:

@Component(service = StampService.class )
public class StampService {
   final static AtomicLong counter = new AtomicLong(0);
   public static class Stamp { long count = counter.incrementAndGet(); }
   
   public Stamp stamp() { return new Stamp(); }   
}

Not only did I feel it was easier to understand, it also allowed you to provide parameters to the instantiation, something that turned out to be really difficult in the generic case. In my case, I also needed some extra information on the factory level. The only way to provide this was with the service properties. It would have been easier and safer to use an API.

The disadvantage is that it takes a few more lines in the simplest case.

However, I needed to refine a service. When someone registers a service Sa, my bundle registers a service Sb. However, we can have any number of Sa’s and thus get multiple Sb’s. This meant you had to track the Sa services with an event method that takes a Component Service Objects for Sa parameter and then manually register a Prototype Service Factory for Sb. It got complex very fast. Worse, it felt like I was back to the early days of OSGi when Service Reference, Bundle Context, and Service Tracker reigned the API.

The easiest solution I came up with in the end is a Service Tracker that tracks Sa services. For each found service, a Prototype object is instantiated for the life time of this service. This Prototype object registers a Prototype Service Factory for the refined service. For each getService() request it returns an instance of from the Sa Service Objects and uses that to create a special instance object that implements the Sa service type.

Since I had two separate refinements, I created a Prototype Mediator utility to hold the common code. It doesn’t look too complicated anymore but I spent a good two days to fully comprehend the problem and had several false starts.

After this painful exercise I am ready to go back to good old typed factories. I do not think this complexity is worth the small gain.

Would be interested if other people have some ideas about the PROTOTYPE scoped components.

Peter Kriens

A. For the real aficionados (and maybe a free review) the Prototype Mediator that I came up with.

package biz.aQute.grapql.gqljava.provider;

import java.util.Collections;
import java.util.Hashtable;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.regex.Pattern;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Filter;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.PrototypeServiceFactory;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.util.tracker.ServiceTracker;

import biz.aQute.closeablevalue.util.CloseableValue;

/**
 * Create a mediator between an input and an output service mediated by a
 * montoring host that can create prototype instances.
 *
 * @param <I> the input service type
 * @param <O> the output service type
 */
public class PrototypeMediator<I, O> implements AutoCloseable {
	final static Pattern																IGNORED_KEYS_P	= Pattern
		.compile("objectClass|component.*|service.*|osgi.*", Pattern.CASE_INSENSITIVE);

	final ServiceTracker<I, Prototype>													tracker;
	final BundleContext																	context;
	final BiFunction<ServiceReference<I>, Map<String, Object>, ServiceMonitor<I, O>>	serviceMonitor;
	final Class<O>																		outType;
	final Object																		inType;

	public interface InstanceRequest<I, O> {
		Object inType();

		Class<O> outType();

		Bundle bundle();

		ServiceRegistration<O> registration();

		ServiceReference<I> inReference();

		I in();
	}

	public interface ServiceMonitor<I, O> extends AutoCloseable {
		Map<String, Object> getServiceProperties();

		CloseableValue<O> create(InstanceRequest<I, O> req);
	}

	class Prototype implements PrototypeServiceFactory<O> {
		final ServiceObjects<I>			factory;
		final Map<O, CloseableValue<O>>	cleanups	= Collections.synchronizedMap(new IdentityHashMap<>());
		final ServiceRegistration<O>	registration;
		final ServiceReference<I>		reference;
		final ServiceMonitor<I, O>		monitor;

		@SuppressWarnings("unchecked")
		Prototype(ServiceReference<I> reference) {
			Hashtable<String, Object> inputProperties = getProperties(reference);
			this.reference = reference;
			this.factory = context.getServiceObjects(reference);
			this.monitor = serviceMonitor.apply(reference, inputProperties);
			Hashtable<String, Object> p = new Hashtable<>(this.monitor.getServiceProperties());
			this.registration = ((ServiceRegistration<O>) context.registerService(outType.getName(), this, p));
		}

		@Override
		public O getService(Bundle bundle, ServiceRegistration<O> registration) {
			I in = factory.getService();
			InstanceRequest<I, O> ir = new InstanceRequest<I, O>() {

				@Override
				public Object inType() {
					return inType;
				}

				@Override
				public Class<O> outType() {
					return outType;
				}

				@Override
				public Bundle bundle() {
					return bundle;
				}

				@Override
				public ServiceRegistration<O> registration() {
					return registration;
				}

				@Override
				public ServiceReference<I> inReference() {
					return reference;
				}

				@Override
				public I in() {
					return in;
				}

			};
			CloseableValue<O> cv = monitor.create(ir);
			O o = cv.get();
			cleanups.put(o, cv);
			return o;
		}

		@Override
		public void ungetService(Bundle bundle, ServiceRegistration<O> registration, O service) {
			try {
				cleanups.remove(service)
					.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		public void close() {
			registration.unregister();
		}

	}

	/**
	 * Create a mediator between an input service type I and an output service
	 * O. I can be a prototype and O is always a prototyped service. The
	 * relation between I and O is mediated by a user supplied function.
	 * <p>
	 * For example, the user registers 2 I services Ia and Ib, both prototype
	 * services. The mediator will then register Oa and Ob. If a protoype is
	 * fetched from Oa, it will fetch a prototype from Ia and combine them
	 * through the mediation function.
	 * <p>
	 * The primary use case is when you need to track a service and refine it.
	 * <p>
	 * The mediator will track all I services. When it finds one, it registers a
	 * service O, which is a Prototype Service Factory. It will have similar
	 * properties.
	 * <p>
	 * Each request for a service, will fetch a service from the related I and
	 * then call the mediation function that returns an CloseableValue<O>, which
	 * is returned. When the instance is no longer needed, the CloseableValue
	 * will be closed.
	 *
	 * @param context the Bundle Context
	 * @param in the type of the incoming service
	 * @param out the type of the outgoing service
	 * @param serviceMonitor the mediation function
	 */
	public PrototypeMediator(BundleContext context, Class<I> in, Class<O> out,
		BiFunction<ServiceReference<I>, Map<String, Object>, ServiceMonitor<I, O>> serviceMonitor) {
		this.context = context;
		this.outType = out;
		this.inType = in;
		this.serviceMonitor = serviceMonitor;
		this.tracker = new ServiceTracker<I, Prototype>(context, in, null) {
			@Override
			public Prototype addingService(ServiceReference<I> reference) {
				return new Prototype(reference);
			}

			@Override
			public void removedService(ServiceReference<I> reference, Prototype service) {
				service.close();
			}
		};

		this.tracker.open();
	}

	/**
	 * See {@link #PrototypeMediator(BundleContext, Class, Class, BiFunction)}
	 *
	 * @param context the Bundle Context
	 * @param in the filter for the incoming service
	 * @param out the type of the outgoing service
	 * @param serviceMonitor the mediation function
	 */
	public PrototypeMediator(BundleContext context, String in, Class<O> out,
		BiFunction<ServiceReference<I>, Map<String, Object>, ServiceMonitor<I, O>> serviceMonitor)
		throws InvalidSyntaxException {
		this.context = context;
		this.outType = out;
		this.inType = in;
		this.serviceMonitor = serviceMonitor;
		Filter filter = context.createFilter(in);
		this.tracker = new ServiceTracker<I, Prototype>(context, filter, null) {
			@Override
			public Prototype addingService(ServiceReference<I> reference) {
				return new Prototype(reference);
			}

			@Override
			public void removedService(ServiceReference<I> reference, Prototype service) {
				service.close();
			}
		};

		this.tracker.open();
	}

	/**
	 * Close the mediator. This will unregister all O services that were created
	 * by the mediation Function and returns all the instances of each I
	 * service.
	 */
	@Override
	public void close() throws Exception {
		this.tracker.close();
	}

	static private Hashtable<String, Object> getProperties(ServiceReference<?> reference) {
		Hashtable<String, Object> properties = new Hashtable<>();
		for (String key : reference.getPropertyKeys()) {
			if (IGNORED_KEYS_P.matcher(key)
				.matches())
				continue;

			properties.put(key, reference.getProperty(key));
		}
		return properties;
	}

}

Prototype scope services are ideally suited for providing context sensitive (or context stateful) instances.

Here’s an example. In JAX-RS, resources are instantiated per request so that request sensitive injections can be performed (injections which are controlled by JAX-RS itself) and/or context sensitive state can be stored. However, if you want to manage said resources using OSGi, doing that natively using services would be incredibly painful. Using prototype scope services for this use case allows this on-demand service instance creation to be enriched by all the OSGi goodness.

1 Like

I also had to make use of prototype-scoped services in some circumstances with the ExtensionFacade implementation, for similar reasons (the components were context-stateful). The alternative pushes some or all of the state-management back onto the client.

PROTOTYPE scoped services felt like going back to OSGi 1998. The beauty of DS is that you can stay in the typed world and suddenly I had to work with OSGi objects again. And not the most friendly.

The second issue I found is that in all my cases I needed some extra information associated with the service. For example, I had a Web Socket but a Web Socket service needs an endpoint URI. Clearly we have service properties but that felt like an abuse because that is not a property to select a service.

Third issue was access to the actual class. When you have an actual service object, you can query its class and find its class loader. I love using the Java type system to add information, like for example the Generic parameter of an interface. With PROTOTYPE scope you can’t because you have no idea how much overhead the creation of the instance entails.

The best solution that seems to cover all cases is to just create a factory service. So for the WebSocket it would look like:

interface WebSocketFactory {
    CloseableValue<WebSocket> create();
    String getEndpoint();
}

I feel, but I maybe wrong, that this is really an enterprise scenario where you inject large graphs of code like CDI or Spring. In the DS case, it just feels writing very hard to read code … But maybe it is because I lack enterprise experience.