Showing posts with label services. Show all posts
Showing posts with label services. Show all posts

Tuesday, March 1, 2016

Adding Aspects to OSGi Services with no Bytecode Weaving

In my previous post I looked at how easy it is to apply the 'branch by abstraction' pattern to OSGi services, where the Service API is the abstraction layer and you can branch at runtime without even taking down the service client.
Another part of the story here is that it can be useful to add aspects to an implementation. This can also be done using the branch by abstraction pattern and in OSGi you can add these aspects to existing services without the need to modify them, and also without the need for bytecode manipulation. In OSGi you can proxy services by hiding the original and placing a proxy service in the service registry that consumers will use instead. This makes it possible to add a chain of aspects to the service invocations without the need to either change the clients nor the services themselves.

To illustrate, I started a little project called service-spector that allows you to add service aspects to any kind of service. The aspects themselves are also implemented as services. Here's what a ServiceAspect looks like:

public interface ServiceAspect {
  String announce();
  void preServiceInvoke(ServiceReference service, Method method, Object[] args);
  void postServiceInvoke(ServiceReference service, Method method, Object[] args, Object result);
  String report();
}

Aspects are called before and after a service invocation is done via preServiceInvoke()/postServiceInvoke(). They are called with a reference to the service being invoked and with the parameters that are provided to the service. The post call is also provided with the result of the invocation. The aspect API allows you to do things like obtaining metrics for services, add logging to existing service calls or anything else really that an aspect could do. Additionally it contains annouce() and report() to present information to the user.

The service-spector is configured using Configuration Admin with a list of filters for services that need have the aspects applied in the service.filters property. There is an additional configuration item that states whether the original services need to be hidden. The proxies are automatically registered with a higher service ranking than the original service. If the consumer of the original only uses a single service the original does not need to be hidden as the one with the higher ranking will be selected. However, if a consumer uses all the services of a certain type you do want to hide the originals. In general it's safer to hide the original service, but if you want to run without the HidingHooks you can switch this off.

I am using the Felix File Install to provide the configuration and have it stored in a file in load/org.coderthoughts.service.spector.impl.ServiceSpector.config A little known feature of File Install is that is supports typed configuration data in .config files, so the following file contains a Boolean hide.services property and a String[] service.filters property.

hide.services=B"true"
service.filters=["(objectClass\=org.coderthoughts.primes.service.PrimeNumberService)",
    "(objectClass\=org.apache.felix.bundlerepository.RepositoryAdmin)"]

In the ServiceSpector main component I can easily consume this configuration using the new Declarative Services annotation based approach:

@Component(immediate=true)
public class ServiceSpector implements ServiceListener {
  // This API is populated by Config Admin with configuration information
  @interface Config {
    String [] service_filters();
    boolean hide_services() default false;
  }

...

  @Activate
  private synchronized void activate(BundleContext bc, Config cfg) {
    ...
    System.out.println("Service filters: " +
      Arrays.toString(cfg.service_filters()));
    System.out.println("Hide services: " + cfg.hide_services());
  }

The Config annotation is automatically populated with the configuration information by matching the property keys with the annotation methods, which I can easily read out using its typed API (the dots in configuration keys are mangled to underscores).

Service-spector has a Service Listener that looks for services appearing and checks if they need to have aspects applied. If so they will be proxied. Then, when the proxied service is invoked, all the ServiceAspect services are called to let them do their thing. For example the CountingServiceAspect simply counts how often a certain API is invoked: 

@Component(scope=ServiceScope.PROTOTYPE)
public class CountingServiceAspect implements ServiceAspect {
  Map<String, LongAdder> invocationCounts = new ConcurrentHashMap<>();

  @Override
  public String announce() {
    return "Invocation counting started.\n";
  }

  @Override
  public void preServiceInvoke(ServiceReference service, Method method, Object[] args) {
    Class declaringClass = method.getDeclaringClass();
    String key = declaringClass.getSimpleName() + "#" + method.getName();
    invocationCounts.computeIfAbsent(key, k -> new LongAdder()).increment();
  }

  @Override
  public void postServiceInvoke(ServiceReference service, Method method, Object[] args, Object result){
    // nothing to do
  }

  @Override
  public String report() {
    StringBuilder sb = new StringBuilder();
    sb.append("Invocation counts\n");
    sb.append("=================\n");
    for (Map.Entry<String, LongAdder> entry : invocationCounts.entrySet()) {
      sb.append(entry.getKey() + ": " + entry.getValue() + "\n");
    }
    return sb.toString();
  }
}


The CountingServiceAspect is included in the Service Spector bundle. Lets add this and the File Install and Configuration Admin bundles to the framework setup used in the previous post that computes primes. I now have the following bundles installed. I removed bundle 9, the incorrect prime number generator that returns only 1's for now.

g! lb
START LEVEL 1
ID|State     |Level|Name
 0|Active    | 0|System Bundle (5.4.0)|5.4.0
 1|Active    | 1|Apache Felix Bundle Repository (2.0.6)|2.0.6
 2|Active    | 1|Apache Felix Gogo Command (0.16.0)|0.16.0
 3|Active    | 1|Apache Felix Gogo Runtime (0.16.2)|0.16.2
 4|Active    | 1|Apache Felix Gogo Shell (0.10.0)|0.10.0
 5|Active    | 1|Apache Felix Declarative Services (2.0.2)|2.0.2
 6|Active    | 1|service (1.0.0.SNAPSHOT)|1.0.0.SNAPSHOT
 7|Active    | 1|impl (1.0.1.SNAPSHOT)|1.0.1.SNAPSHOT
 8|Installed | 1|client (1.0.0.SNAPSHOT)|1.0.0.SNAPSHOT
10|Active    | 1|Apache Felix Configuration Admin Service (1.8.8)|1.8.8
11|Active    | 1|Apache Felix File Install (3.5.2)|3.5.2
12|Active    | 1|service-spector (1.0.0.SNAPSHOT)|1.0.0.SNAPSHOT


Additionally I have the configuration file from above in the ./load/org.coderthoughts.service.spector.impl.ServiceSpector.config location. This is the default location where the Felix File Install looks. When I start the service-spector bundle it reports the active aspects and its configuration:

g! start 12
Invocation counting started.

Service Spector
===============
Service filters: [(objectClass=org.coderthoughts.primes.service.PrimeNumberService), (objectClass=org.apache.felix.bundlerepository.RepositoryAdmin)]
Hide services: true


Ok so let's start our client:
g! start 8
Services now: $Proxy6(PrimeNumbers)
First 10 primes: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
First 10 primes: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
First 10 primes: 2, 3, 5, 7, 11, 13, 17, 19, 23, 29
g! stop 8


The version of primes on master has a PrimeServiceReporter that reports the name and the classname of the service. Here we can see that the service calls itself "PrimeNumbers" but that the actual service instance is a Proxy.

As our filters state that we are also proxying the Repository Admin which comes with the Felix implementation we can also exercise that, for example via the shell commands:
 

g! obr:repos list
... some output ...
g! obr:list
... some output
 

Ok so we've now used both of the services that are being proxied. Let's see what APIs have been utilized. The service spector calls report() on all the ServiceAspects when the bundle is being stopped:

g! stop 12
Invocation counts
=================
PrimeNumberService#getServiceName: 1
PrimeNumberService#nextPrime: 30
RepositoryAdmin#listRepositories: 1
RepositoryAdmin#discoverResources: 2

Contributing more aspects

Aspects are contributed as services themselves so they don't need to be part of the service-spector bundle itself. The service-spector-method-timer bundle is a separate bundle that contributes a ServiceAspect that provides average method invocations times.

Get the binaries

The code in this blog post can easily be built using maven, but if you just want the binaries, you can get them from here:
service-spector: https://github.com/coderthoughts/service-spector/releases
method-timer: https://github.com/coderthoughts/service-spector-method-timer/releases

Wednesday, November 18, 2009

Altering OSGi Service Lookups with Service Registry Hooks

OSGi Services are great. They can solve many problems very elegantly. Take for instance SPI patterns. SPI patterns are used to make implementations pluggable. This type of pattern outside OSGi often comes with a bunch of external configuration (e.g. in META-INF/services) that makes it hard to manage and gives you a once-off chance to choose your implementation that you're stuck with for the rest of the VM lifecycle. OSGi Services provide a much more elegant way to solve this. 
 
In many cases clients simply want to use some functionality and don't particularly care how that functionality was created. When using OSGi Services the functionality is looked up in the OSGi Service Registry which is a directory of these. Services can be looked up by implemented interface (like a phone book) or by provided attribute (like the yellow pages). How the service got there is not interesting to the client and he's not involved in that. What if the service gets replaced while the client is active? No problem, the OSGi Service Programming model is actually built around this dynamicity. You don't need to stop service consumers when you are replacing or updating the service, they are automatically rebound.

The Service Registry also provides us with an attribute based selection mechanism which is very nice if there are multiple implementations to choose from. As an example, take a system where every available printer is represented in the Service Registry as a Service that implements the org.acme.Printer interface. Each printer will have additional properties registered that help with the selection:



When looking for a Printer OSGi allows you to use an LDAP-style filter to select the printer you want. So if you want a printer that can do A3 you would use this LDAP filter:
 
(&(objectClass=org.acme.Printer)(paper-size=A3))

or if you want a printer in a location that starts with 'b' you do this:
 
(&(objectClass=org.acme.Printer)(location=b*))

This is all great and you can build your system on this using either plain OSGi code or component frameworks such as OSGi Blueprint or OSGi Declarative Services. However sometimes you may want to tweak the properties at a later date without having to rewrite your system. Assume that in your organisation all of a sudden the office numbering has changed, which would really mean that you'd have to update the 'location' attribute of all the printers. Or maybe you want to add some additional metadata to existing printers that influence the selection process, like their energy rating.
Service Registry Hooks provide the building blocks that make this possible. I wrote a little bundle called ServiceJockey that uses these to manipulate service registrations and how they are looked up. BTW you can find details at the end of this posting about getting ServiceJockey, which is open source and available freely under the Apache License.

Service Registry Hooks: what are they?
One of the driving factors for Service Registry Hooks (an OSGi standard introduced in the 4.2 Core specification) was the Remote Services (Distributed OSGi) work done in the OSGi Alliance. One of the things that implementations of the Remote Service spec need to know is what kind of services consumers are looking for so that they can go out to a discovery system to see if it might be available remotely. Eagerly registering all available remote services is clearly not scalable so we need a smarter mechanism to allow for transparent on demand discovery of remote services. This is what the ListenerHook and FindHook provide us. Together they allow us to find out what service consumers are requesting making us to only look in a remote Discovery system for those that are relevant to the current framework.

Another problem was frequently brought up. What if you have an existing bundle that doesn't know anything about Remote Services? What is the default behaviour? Should all service lookups all of a sudden always include remote services? The Remote Services spec does include a special property that you can add to your filter to influence this (service.imported) but you clearly don't want to break up all your existing service consumers to add this extra property to their lookup filters.
It turned out that there wasn't really a valid answer to that question applicable to all cases. If you're working on a system only using Remote Services for a select set of services it may make sense to default all other services consumers to not use Remote Services. On the other hand, maybe if you're building a Cloud-based infrastructure where services can move freely from one container to another the default behaviour could be that you do allow remote services for just about anything.
Since there isn't really a one-size fits-all here the Service Registry Hooks make it possible to provide a policy for this problem in a separate bundle. You can create EventHooks and FindHooks to influence what services consumers can see. This effectively allows you to add extra conditions to the lookup of service consumers without the need to modify those consumers.

Finally, EventHooks and FindHooks, together with the a ServiceListener, allow you to proxy Service Registrations, where you provide a second registration of the same object with modified properties, hiding the original.
The ability to provide an alternate registration on the fly and the possibility to impose extra conditions on existing service consumer lookups is what I built ServiceJockey around.



ServiceJockey doesn't replace the Service Object, it only effectively replaces the registration in the Service Registry plus it can put additional constraints on client lookups. But you could go further. If you wanted to actually replace or shadow the real service object and do some work when somebody uses it (maybe log it or something) you could put an additional object between the consumer and the original service object.

Because the Service Registry Hooks effectively modify some basic behaviour of the framework (the visibility of Services) they should really be started early in the Framework lifecycle. You can achieve that by giving them a low start level.
The Service Registry Hooks are available in Felix 1.8.0 and Equinox 3.5 and newer versions of these.

Altering Service Registrations

Let's start with a bundle that consumes (uses) a Printer. It isn't interested in which printer, as long as it can print A4:
BundleContext context = ... // from Activator.start()
Filter filter = context.createFilter(
  "(&(objectClass=org.acme.Printer)(paper-size=A4))");
st = new ServiceTracker(context, filter, null) {
  public Object addingService(ServiceReference ref) {
    // print out some information on the printer
    // or use the printer
    return super.addingService(ref);
  }
};
st.open();

When I run this it's reporting both printers that I have in my system:
Printer:
objectClass: [org.acme.Printer]
service.id: 27
name: p1
location: b283
capabilities: [Double-sided]
paper-size: [A3, A4]


Printer:
objectClass: [org.acme.Printer]
service.id: 28
name: p7
location: a12
capabilities: [Colour, Staple]
paper-size: [A4, Letter]


So the client has arbitrary access to both of them. Now lets see if you can modify the client behaviour so that it only uses a printer with an Energy Rating < 50.
But hold on, we don't even know about energy ratings yet! We need to add this information to the Printer registration without changing the bundle(s) that register the Printers. Let's user Service Jockey to do this.

Service Jockey uses the OSGi Extender Model. This means that it is driven from a data file in a bundle.
So I've got a bundle (called PrinterJockey) that contains META-INF/sj.xml:
<service-jockey xmlns="http://www.coderthoughts.org/schemas/sj/v1.0.0">
 <proxy-registration>
  <rule>
   <service-filter>(&(objectClass=org.acme.Printer)(name=p1))</service-filter>
   <add-property key="energy-rating">75</add-property>
  </rule>
  <rule>
   <service-filter>(&(objectClass=org.acme.Printer)(name=p7))</service-filter>
   <add-property key="energy-rating">50</add-property>
  </rule>
 </proxy-registration>
</service-jockey>


I will have to tell the Service-Jockey that my bundle contains configuration for it, for that I'm adding a header to the Manifest:
Service-Jockey: META-INF/sj.xml


Lets run the Printer Consumer bundle together with Service Jockey and my 'Printer Jockey' configuration bundle that contains the sj.xml file:
id State Level Bundle
0 ACTIVE  0 org.eclipse.osgi_3.5.1.R35x_v20090827
1 ACTIVE  1 org.coderthoughts.servicejockey_0.0.1
2 ACTIVE  1 PrinterJockey_1.0.0
3 ACTIVE 10 Printers_1.0.0


In my case the Printers bundle both registers and consumes the Printers, but that's because its a little test bundle. It's not really relevant. Note that the Printers bundle has a higher start level than the Jockey ones and therefore starts later.


When I run my system again I can see that it's doing some work. Now my client reports these services:
Printer:
objectClass: [org.acme.Printer]
service.id: 30
name: p1
location: b283
capabilities: [Double-sided]
paper-size: [A3, A4]
energy-rating: 75
.ServiceJockey: Proxied


Printer:
objectClass: [org.acme.Printer]
service.id: 32
name: p7
location: a12
capabilities: [Colour, Staple]
paper-size: [A4, Letter]
energy-rating: 50
.ServiceJockey: Proxied


Aha! I've got my energy rating in there! That's good. Remember that these are alternative registrations. The original ones haven't changed, they're just hidden.

Altering Client Side Lookups

Now I want my client to only use the one that is rated <= 50.
That's done by adding another rule to the Service Jockey configuration. A rule that adds an extra filter to any matching lookup in a consumer.
<service-jockey xmlns="http://www.coderthoughts.org/schemas/sj/v1.0.0">
 <restrict-visibility>
  <rule>
   <bsn>.*</bsn>
   <service-filter>(objectClass=org.acme.Printer)</service-filter>
   <add-filter>(energy-rating<=50)</add-filter>
  </rule>
 </restrict-visibility>
 <proxy-registration>
  ...
 </proxy-registration>
</service-jockey>


You can specify a regular expression to say what the Bundle Symbolic Name of the consumer bundle(s) is that the rule applies to. In my case .* matches anything. So it applies to any bundle that has OSGi Service consumers. By the way, there's also a tag.


You specify to what services the additional filter applies with the tag and you specify the additional filter in . In my case I'm adding the (energy-rating<=50) condition to any Printer Service returned to a consumer (the condition looks a bit dodgy because of the XML escaping of the '<' sign - you could use a CDATA section instead...).
Note that the Service Filter applies to any service returned to a service consumer, regardless of the filter used by the client itself looks like.


Let's run the client again:
Printer:
objectClass: [org.acme.Printer]
service.id: 32
name: p7
capabilities: [Colour, Staple]
location: a12
paper-size: [A4, Letter]
energy-rating: 50
.ServiceJockey: Proxied


Bingo - it only selects the service I wanted it to select. Based on additional properties and criteria listed in my Service Jockey configuration file. I did not have to modify the Printer Service registration bundle or the Consumer bundle...

Is it overkill?

Seems like a lot of work for just adding a service property, is there not a lighter way to at least add values to Service Registrations? Well at least that was my initial impression. However looking at the ServiceJockey bundle, it's only 14kb. Besides that there is currently no other way to achieve this...

Service Jockey - ride your service interactions

I didn't show any of the code to actually use the EventHook and the FindHook. Have a look at the HidingEventHook and HidingFindHook source code for that.

You can check out the source (Apache License) as an Eclipse Project from SVN here. If you fancy modifying the code, I would recommend you also get the tests project and add new ones. The test bundle depend on the Mockito bundle for its mock objects...

I'm sure the Service Jockey isn't perfect, because I only wrote it during an airplane flight so feel free to send patches :)

Also in the source tree, Eclipse projects for the Printers and PrinterJockey example bundles.