An application I am currently developing is written in a functional programming style using scala-cats' IOApp.
Now the problem is that I need to deploy this application in an OSGi context that does not really seem to fit my functional approach.
My main method looks like this:
object Main extends IOApp {
override def run(args: List[String]): IO[ExitCode] = for {
_ <- IO(println("Starting with args: " + args.mkString(",")))
myProgram = new MyProgram(/*...*/)
_ <- myProgram.run() // def run(): IO[RunResult]
// ...
_ <- myProgram.exit() // def exit(): IO[Unit]
} yield ExitCode.Success
}
Now to deploy this to OSGi I had to write a BundleActivator:
import org.osgi.framework.{BundleActivator, BundleContext}
class Activator extends BundleActivator {
private var myProgram: Option[myProgram] = None
override def start(context: BundleContext): Unit = {
myProgram = Some(new MyProgram(/*...*/))
myProgram.foreach{ _.run().unsafeRunSync() }
}
override def stop(context: BundleContext): Unit = {
myProgram.foreach{ _.exit().unsafeRunSync() }
}
}
As you can see this Activator I came up with is far from being written in a functional manner. Is there any way I can at least get rid of var myProgram (the mutable var to be specific)? I can't seem to figure out how that would be possible.
Edit:
The activator needs to be defined in the manifest, so this is part of my build.sbt:
packageOptions := Seq(ManifestAttributes(
("Bundle-Activator", "my.package.Activator"),
...))
There's no way to avoid some nasty Java-esque code here, this is just how OSGi works. But what you can do is hide the nastiness in a reusable class, so you only need to get it right once and never look at it again. My suggestion would be a generic Activator that will acquire a cats Resource on startup and dispose of it it when you shut it down.
class ResourceActivator(resource: BundleContext => Resource[IO, Unit])
extends BundleActivator {
private var cleanup: IO[Unit] = null
override def start(context: BundleContext): Unit =
cleanup = resource(context).allocate.unsafeRunSync()._2
override def stop(context: BundleContext): Unit =
cleanup.unsafeRunSync()
}
Valid OSGi implementations will never call stop without calling start first, so it's OK to initialize cleanup to null.
Note that the above class easily allows you to e. g. start some asychronous computation in the start method and shut it down in the stop method:
class MyActivator extends ResourceActivator(ctx =>
for {
res1 <- someResource
res2 <- anotherResource
_ <- Resource.make(doSomeStuff.start)(_.cancel)
} yield ()
)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With