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 SuperServiceImpl
This 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);
if (ref != null) {
SuperService s = (SuperService) context.getService(ref);
System.out.prinln(s.msg);
context.ungetService(ref);
}
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.
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 parameter
If we reverse this rule to the DSL version for using a service we get
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:
context.service(classOf[SuperService]).get({s =>
println(s.msg)
})
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(...) // Version 1
context event "APP/TOPIC" ! new MyEvent(...) // Version 2
// receiving events
// Version 1
context event "APP/TOPIC" receive match {
case MyEvent(e) => ...
case _ => ...
}
// Version 2
context event "APP/TOPIC" ? match {
case MyEvent(e) => ...
case _ => ...
}
// Wire admin
context wire classOf[MyType] connect new MyReciever
What do you think? Which cases would you like to see covered by a DSL?