Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala-cat's IOApp in an OSGi context

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"),
  ...)) 
like image 643
Florian Baierl Avatar asked Jan 26 '26 05:01

Florian Baierl


1 Answers

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 ()
)
like image 161
Matthias Berndt Avatar answered Jan 28 '26 20:01

Matthias Berndt