Creating a Domain Specific Language for OSGi
Whereas Scala is probably the most interesting language, the OSGi Technology provides a good candidate for the best framework within the Java system ;-) So what would be more obvious than looking at a combination of them? Of course, since Scala generates Java byte code and the OSGi framework is dedicated to Java this is already possible. Neil Bartlett wrote a nice introduction on how to build an OSGi bundle in Scala.
However, we can do even better! The Scala language syntax provides several mechanisms to create a domain specific language. Lets start with a simple bundle activator written in Scala:
class MyBundleActivator extends BundleActivator {
def start(context: BundleContext) {}
def stop(context: BundleContext) {}
}
Nothing special here. Now lets assume we have the following definitions of a service interface and implementation:trait SuperService {
def msg: String
}
class SuperServiceImpl extends SuperService {
def msg = "My super message!"
}
To add an instance of the service implementation to the service registry we normally would write:context.registerService(classOf[SuperService].getName, new SuperServiceImpl, null)But with some work and leveraging the syntactic elements of Scala, we can write:
context service classOf[SuperService] add new SuperServiceImplThis doesn't save a lot of typing but is already much more readable. A nice side effect is, that the compiler ensures that the implementation is a subclass of the interface. So this statement would not compile:
context service classOf[SuperService] add new String("A fake super service")Using this kind of syntax really pays off if we want to access a service:context service classOf[SuperService] get {s =>
println(s.msg)
}Again,
the compiler ensures that everything is correctly typed. Thanks to the
type inference mechanism, we do not need to declare that the type of
variable s is SuperService. So we can directly call s.msg and pass it
to the println methods. If we would have tried to call a nonexistent
method, e.g. msg2, the compiler would have thrown an error. The Java
equivalent to the above code is:ServiceReference ref = context.getServiceReference(SuperService.class);While the DSL version is much more readable and less error-prone, it also has the advantage of following the RAII design pattern. We do not need to do the null-check and the DSL library ensures to "unget" the service.
if (ref != null) {
SuperService s = (SuperService) context.getService(ref);
System.out.prinln(s.msg);
context.ungetService(ref);
}
So how does this work? First, lets remove some of Scala's syntactic sugar: In Scala, a method that only has one parameter can also be used as an infix operator. So
obj.method(parameter)can be used as
obj method parameterIf we reverse this rule to the DSL version for using a service we get
context.service(classOf[SuperService]).get({s =>
println(s.msg)
}) The get method is a higher-order function that passes the reference to the service to a closure. You might find this in future versions of Java (BGGA, CICE, FCM, C3S).
More interesting however, is the context.service(...) part of the
statement. context is an instance of the BundleContext class that
clearly does not have a service(...) method. The trick here is to use
an implicit conversion from BundleContext to RichBundleContext. The
RichBundleContext has the method service(...) which in turn returns an
instance of the class ServiceInformation. This class provides the add
and get methods:class RichBundleContext(context: BundleContext) {
def service[T](s: Class[T]) = new ServiceInformation[T](context, s)
}
class ServiceInformation[T](...) {
def add(...) = ...
def get(...) = ...
}To convert the BundleContext instance to an RichBundleContext we could either use the constructor directly or we could use a factory method:
// constructor
new RichBundleContext(context).service(classOf[SuperService]).get({s =>
println(s.msg)})
// factory method
implicit def bc2rbc(bc: BundleContext): RichBundleContext = {
new RichBundleContext(bc)
}
bc2rbc(context).service(classOf[SuperService]).get({s => println(s.msg)})
You
may have noticed that the bc2rbc method is marked "implicit". So as
long as this method is in the local scope (e.g. imported) and
"implicit", Scala will automatically apply this method. So
context.service(...) automatically becomes
bc2rbc(context).service(...). Several conditions must match to make
this happen. Please take a look at the Scala documentation for more
informations.
There are several use-cases for a custom DSL in OSGi:
// sending events
context event "APP/TOPIC" send new MyEvent(...)
// receiving events
context event "APP/TOPIC" receive match {
case MyEvent(e) => ...
case _ => ...
}
// Wire admin
context wire classOf[MyType] connect new MyReceiver
What do you think? Which cases would you like to see covered by a DSL?
- Login or register to post comments
- 2568 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)









