Friday, March 18, 2011

Metatypes

Many, many, many years ago I worked a lot with LDAP and the ISO communication standards like X.500 and X.700. Looking at the Metatype specification I am afraid I had some bad influence on that spec. Looking back, I probably would be able to make it a bit simpler today, however, I do believe that it is one of the specifications in the that under-appreciated. Actually, a lot of people have no clue what it is.

So what problem does the Metatype spec solve? The problem it addresses is runtime configuration. The OSGi Configuration Management specification makes it possible to configure bundles with properties. The problem we faced that gave us Metatype is that we needed a schema to describe those properties to editors and validators. At the time I was involved in creating automatically created user interfaces based on type information. The Metatype specification provided such type information for the configuration properties. In the first incarnation of Metatype we did not describe a format (seems silly and I forgot why), only hard to use Java interfaces. Later versions added a Metatype Service and an XML based format for describing the properties.

The Metatype specication has been in existence for quite a long time there are as far as I know very few editors built for it. Actually, the only one I am familiar with is the Apache Felix Webconsole. I am very interested in Metatypes because the triplet metatype + configuration + components (DS) provides a surprisingly powerful programming model. DS can control the life cycle of components based on the configuration data that is available. Creating a factory configuration causes the activation of a component (if it is satisfied of course) and deleting that configuration deactivates the component. Each component is provided with the properties of that factory instance. For example:

@Component(configurationPolicy=required)
 public class FactoryDemo {
   @Activate
   void activate( Map props ) {
      ...
   }
 }

Such a component will follow the life cycle of a configuration or factory configuration of the (factory) PID com.example.FactoryDemo (the name of the component). This model is one of the cornerstones of good use of OSGi: configuration driven design. This model allows us to register services that are under control of the deployer. For example, assume you want to make an interface to Amazon S3. Where do you store your acces key and secret key? If you have any real world experience you will of course not hard code them. So in a good design you would write the following code would register a Store service:

@Component(configurationPolicy=required)
  public class BlobStoreComponent implements Store {
    String domain;
    AmazonS3Client client;

    @Activate
    void activate( Map props ) {
      String secretKey = (String) props.get(".secret.key");
      String accessKey = (String) props.get(".access.key");
      AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); 
      client = new AmazonS3Client(credentials);
      domain = (String) props.get("domain");
    }
    ...
  }

With this component, the deployer can now decide how many, and for which domains a BlobStore service should be registered. This is quite a big step in information hiding with very little work (try doing this in other environments). However, setting these properties is always awkward, to say the least. Adding a Metatype file in the bundle would make the editing easier. Set lets add a Metatype XML file in OSGI-INF/metatypes/com.example.BlobStore.xml:

«metatype:MetaData xmlns:metatype='http://www.osgi.org/xmlns/metatype/v1.1.0'»
  «OCD 
    name='Blob store component config' 
    id='aQute.metatype.aws.BlobStoreComponent' 
    localization='aQute.metatype.aws.BlobStoreComponent$Config'»
    «AD name='Domain' id='domain' cardinality='0' required='true' type='String'/»
    «AD name='Secret key *' id='.secretKey' cardinality='0' required='true' type='String'/»
    «AD name='Access key *' id='.accessKey' cardinality='0' required='true' type='String'/»
    «AD name='Region' id='region' cardinality='0' required='true' type='String'»
      «Option label='US standard' value='US_Standard'/»
      «Option label='US west' value='US_West'/»
      «Option label='EU ireland' value='EU_Ireland'/»
      «Option label='AP singapore' value='AP_Singapore'/»
    «/AD»
  «/OCD»
  «Designate pid='aQute.metatype.aws.BlobStoreComponent'»
    «Object ocdref='aQute.metatype.aws.BlobStoreComponent'/»
  «/Designate»
«/metatype:MetaData»

This gives us a decent GUI on the webconsole:


Unfortunately, to get this GUI it looks like we're back in XML hell? Nope! bnd comes to the rescue! In the latest release 1.42 there is really nice support for create the Metatypes from a Java interface, using optional annotations to provide additional information. The previous GUI was created automatically from the following interface:

interface Config {
   String domain();
   String _secretKey();
   String _accessKey();
   Region region();
 };

The title is derived from the configuration interface name and the property key labels come from the method names, it uses the camel case and characters like $ and _ to turn them into something more pleasant to read for the labels. The _ in front of secretKey and accessKey indicate private properties, they will not be registered as a service property. Notice that he Region, which is an Amazon enum, is used to create a popup list with readable labels and the exact names of the enum. bnd will automatically create this for you. So what do you have to do to make this all work?

@Component(designate=BlobStoreComponent.Config.class,immediate=true)
 public class BlobStoreComponent implements Store {
  interface Config {
    String domain();
    String _secretKey();
    String _accessKey();
    Region region();
  };
  Config config;
  AmazonS3Client client;
 
  @Activate
  void activate(Map map) {
    config = Configurable.createConfigurable(Config.class, map);
    AWSCredentials credentials = new BasicAWSCredentials(
       config._accessKey(), config._secretKey());
    client = new AmazonS3Client(credentials);
    client.createBucket(config.domain(),config.region());
  }

  public InputStream get(String name) {
    S3Object obj = client.getObject(config.domain(), name);
    return obj.getObjectContent();
  }

  public void put(String name, InputStream input) {
    client.putObject(config.domain(), name, input, null);
  } 
 }
This is all you have code ... No property handling, not need to check for null properties (by default the fields are required), not mucking around deep in XML, all type safe ... The same package that contains the optional  annotations contains a tiny little class that creates a proxy that maps the methods to the property and performs type conversion if needed.

If you want to experience OSGi heaven (or at least the road towards OSGi heaven), download the alpha version of bndtools and play with it. This code and other samples are provided so you can directly use them with bndtools. You can find  this code at an aQute github repository.The Metatype and Components chapter of the bnd manual define the details.

For me, the metatype support is fixing a crucial extra step to take advantage of OSGi. I am looking forward to make many examples in the future that take advantage of the inredible power of metatype + configuration + components. For ant lovers, the latest version is available from the download page. For maven, this will require some collaboration with the maven bundle guys but I will work on this. Enjoy, and hope to see you next week at OSGi Devcon/EclipseCon!

Peter Kriens

PS. The hackathon (or hackaton as I erroneously called it) is on Thursday morning but I do not have the room yet. If you're coming send me a mail and I'll keep you posted.

3 comments:

  1. Hi Peter

    This is great news :-). In our product we are also generating (web - GWT) GUI for configuration forms from metatype. Currently all is written in metatype.xml-s.

    Our forms support little more than metatype can describe - for example dynamic enumerations where options are result of calling one of enumerable services. Other attribute could be whether String is multi-line or single-line. Or input box with suggestions (editable enumeration). Also validation constraints... XML can be of course extended but I found no means in metatype API to access it. Or did I miss something?

    About this new feature in bnd - will I need to install bndlib or will Configurable be in different (runtime) bnd bundle? And does it cover all the capabilities of metatype (eg. optional properties)? I know can check it myself but I think it would be good to know for other readers :-)

    Thanks in advance.

    --
    Martin Ždila

    ReplyDelete
  2. I think another step could be to read Bean Validation annotations. They contain a lot of information about attributes that could be used for GUIs (See http://jcp.org/aboutJava/communityprocess/final/jsr303/index.html). I don´t know how much it resemlbes Metatypes but I think there are a lot of similarities.

    ReplyDelete
  3. @Martn: The Configurable is in the aQute.bnd.annotations.metatype package so the runtime support is minimal but you will have to include this small jar in runtime or use the following line in your bnd.bnd file in bndtools:

    Conditional-Package: aQute.bnd.*

    This will only include the code when you actually use it.

    @Christian: Good idea to use the Bean validation. However, looking at it, it looks like they went a bit overboard and some of the semantics will be hard to capture with the metatypes.

    ReplyDelete