Introduction
This post deals with my adventures in Scala and applying them to a real world problem. That problem is addressing some of the complexities that come with dealing with River services (previous “Jini” services).
For those who don’t know, River/Jini services are hugely powerful and customisable, but such things always come with a price. The price in this case is a reputation – probably deserved – that setting them up and using them is difficult and complicated. It’s my personal opinion that making a simple setup easy to configure and use is necessary to encourage adoption of what is a very smart and clever bit of kit.
This Scala work therefore addresses two problems. The first, making River services easy to use and the second? Getting some practical experience writing real Scala code.
This code is available in the Apache River skunk branch “easystart”.
Getting Started
River services reside in what we refer to as a djinn. This term, although present in the specification, isn’t used in the code; until now.
In the same “easystart” skunk branch are some helper classes for starting the infrastructure services that come with the River distribution. These are;
- HTTP server
- Lookup Service (reggie)
- Java Space (outrigger)
- Transaction Service (mahalo)
Look for the classes;
-
org.apache.river.extra.easystart.StartHttpServer
-
org.apache.river.extra.easystart.StartLookupService
-
org.apache.river.extra.easystart.StartOutriggerService
-
org.apache.river.extra.easystart.StartMahaloService
If using these classes as the basis for your djinn you will also need to supply the following VM arguments.
-Djava.security.policy=$RIVER_HOME/src-extra/policy.all
-Djava.rmi.server.RMIClassLoaderSpi=net.jini.loader.pref.PreferredClassProvider
At time of writing, the code in “easystart” is not tidy nor suitable for a real deployment, it began as a Proof Of Concept and hasn’t yet made it into sensible code.
Additional documentation can be found in the JavaDocs of these classes.
So you can either use the above or whatever other mechanisms you use to start your services. Remember, to continue you will need at least a HTTP server and one Lookup Service.
Accessing the Djinn
So now we need to access our djinn. Actually, we first need to know how we’re going to discover our djinn. Currently we have two options;
- Multicast
- Unicast
Multicast is easier to setup, we essentially just see what’s going on in our current subnet. Unicast isn’t that much more difficult, but it does require us to know where at least one lookup service (also known as a “Service Registrar” or by it’s implementation codename “reggie”) is. So let’s do that now in Scala.
Multicast
val djinn = new Djinn(new MulticastDiscovery)
Unicast
val locators
= Array[LookupLocator] (
new LookupLocator("jini://hostname"))
val djinn = new Djinn(new UnicastDiscovery (locators))
As you can see, there isn’t really much to it setting up our djinn. Both of these methods have now made available many of the methods available to us that we need to start using services.
Using the Djinn
We’re now in a position to start using our djinn. So what do we want to do? Maybe we want to write an entry into every space.
val spaces = new ServiceTemplate(
null,
Array[java.lang.Class[_]](classOf[JavaSpace]),
null)
val writeEntry = { si: ServiceItem => //code to write in a space
val writeInAll =
{ sis: List[ServiceItem] => Some(sis map writeEntry) }
djinn filter spaces flatMap writeInAll
It’s really that simple, we take our djinn, filter it looking for spaces and then we execute “writeInAll” on every space. The only slightly confusing bit here is the call to “flatMap“.
Of course, we can use filter with a more specific ServiceTemplate to return a more specific kind of service that we want to do something interesting with.
Or maybe we want to shut down all the services.
val terminate = (si:ServiceItem) =>
...//code to terminate a service
djinn foreach terminate
RemoteExecution
As you’re probably aware, every time you do something to a River service there is the possibility of a java.rmi.RemoteException of being thrown. I’ve chosen to adopt something similar to Scala’s Option to hide this away.
So when you call functions on a djinn you’ll actually get back a RemoteExecution[A] with “A” being of a type you’re looking for. RemoteExecution can either be a Result or a Problem. Take a look at the source code for these things and it’s fairly obvious what’s going on inside them. I decided not to just have djinn function return Option‘s because in the case of an exception being thrown we potentially want to know what the exception was.
To complicate things slightly further. A Result itself can contain an Option thus indicating whether something or nothing was returned. We need this additional complexity because when we search for a service (for example) one of three things might happen.
An exception might get thrown, in which case we’ll receive a RemoteExecution.Problem with the exception inside it. Nothing might be returned, in which case we’ll get a RemoteExecution.Result.None. Finally, we might actually get the service we were looking for, so we’ll get a RemoteException.Result.Some(…).
Talking to the Djinn
When writing “pure” River code it’s possible to register listeners to your discovery mechanism so you know when services are made available, destroyed or changed. This is possible when using a djinn as well. We do this slightly differently in Scala.
djinn.handleNewService(
{event: ServiceDiscoveryEvent => println("New Service")})
djinn.handleRemovedService(
{event: ServiceDiscoveryEvent => println("Removed")})
djinn.handleChangedService(
{event: ServiceDiscoveryEvent => println("Changed")})
All we do for a djinn is register functions to be called on each event. The function needs to have the signature (f: ServiceDiscoveryEvent => Unit), and that’s all there is to it.
Finishing
Shutting down a djinn follows the same approach as everything else in River.
djinn terminate
Conclusion
At the time of writing, these classes are obviously incomplete. The Scala ones in particular are pretty raw and I’m sure that there are Scala based optimisations and patterns that I’ve missed when writing them. We all have to start somewhere though.
Please drop a message onto the dev@river.apache.org mailing list with any comments, requests or suggestions for improvement.
Posted by Tom 