Question

I am writing a Play (2.2) controller in Scala, which should return the result of a query against OrientDB. Now, I have succeeded in writing a synchronous version of said controller, but I'd like to re-write it to work asynchronously.

My question is; given the below code (just put together for demonstration purposes), how do I re-write my controller to interact asynchronously with OrientDB (connecting and querying)?

import play.api.mvc.{Action, Controller}
import play.api.libs.json._
import com.orientechnologies.orient.`object`.db.OObjectDatabasePool
import java.util
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery
import scala.collection.JavaConverters._

object Packages extends Controller {
  def packages() = Action { implicit request  =>
    val db = OObjectDatabasePool.global().acquire("http://localhost:2480", "reader", "reader")
    try {
      db.getEntityManager().registerEntityClass(classOf[models.Package])
      val packages = db.query[util.List[models.Package]](new OSQLSynchQuery[models.Package]("select from Package")).asScala.toSeq
      Ok(Json.obj(
        "packages" -> Json.toJson(packages)
      ))
    }
    finally {
      db.close()
    }
  }
}

EDIT:

Specifically, I wish to use OrientDB's asynchronous API. I know that asynchronous queries are supported by the API, though I'm not sure if you can connect asynchronously as well.

Attempted Solution

Based on Jean's answer, I've tried the following asynchronous implementation, but it fails due to a compilation error value execute is not a member of Nothing possible cause: maybe a semicolon is missing before 'value execute'?:

def getPackages(): Future[Seq[models.Package]] = {
    val db = openDb
    try {
      val p = promise[Seq[models.Package]]
      val f = p.future
      db.command(
        new OSQLAsynchQuery[ODocument]("select from Package",
          new OCommandResultListener() {
            var acc = List[ODocument]()

            @Override
            def result(iRecord: Any): Boolean = {
              val doc = iRecord.asInstanceOf[ODocument]
              acc = doc :: acc
              true
            }

            @Override
            def end() {
              // This is just a dummy
              p.success(Seq[models.Package]())
            }
          // Fails
          })).execute()
      f
    }
    finally {
      db.close()
    }
}
Was it helpful?

Solution

One way could be to start a promise, return the future representing the result of that promise, locally accumulate the results as they come and complete de promise ( thus resolving the future ) when orient db notifies you that the command has completed.

def executeAsync(osql: String, params: Map[String, String] = Map()): Future[List[ODocument]] = {
    import scala.concurrent._
    val p = promise[List[ODocument]]
    val f =p.future
    val req: OCommandRequest = database.command(
      new OSQLAsynchQuery[ODocument]("select * from animal where name = 'Gipsy'",
        new OCommandResultListener() {
          var acc = List[ODocument]()
          @Override
          def result(iRecord:Any):Boolean= {
            val doc = iRecord.asInstanceOf[ODocument]
            acc=doc::acc
            true
          }

          @Override
          def end() {
            p.success(acc)
          }
        }))
    req.execute()
    f
  }

Be careful though, to enable graph navigation and field lazy loading, orientdb objects used to keep an internal reference to the database instance they were loaded from ( or to depend on a threadlocal database connected instance ) for lazily loading elements from the database. Manipulating these objects asynchronously may result in loading errors. I haven't checked changes from 1.6 but that seemed to be deeply embedded in the design.

OTHER TIPS

It's as simple as wrapping the blocking call in a Future.

import play.api.libs.concurrent.Execution.Implicits.defaultContext
import scala.concurrent.Future

object Packages extends Controller {
  def packages = Action.async { implicit request  =>
    val db = OObjectDatabasePool.global().acquire("http://localhost:2480", "reader", "reader")
    db.getEntityManager().registerEntityClass(classOf[models.Package])

    val futureResult: Future[Result] = Future(
      db.query[util.List[models.Package]](new OSQLSynchQuery[models.Package]("select from Package")).asScala.toSeq
    ).map(
      queryResult => Ok(Json.obj("packages" -> Json.toJson(packages)))
    ).recover { 
      // Handle each of the exception cases legitimately
      case e: UnsupportedOperationException => UnsupportedMediaType(e.getMessage)
      case e: MappingException              => BadRequest(e.getMessage)
      case e: MyServiceException            => ServiceUnavailable(e.toString)
      case e: Throwable                     => InternalServerError(e.toString + "\n" + e.getStackTraceString) 
    }

    futureResult.onComplete { case _ => 
      db.close()
    }

    futureResult
  }
}

Note that I did not compile the code. There is a lot of room to improve the code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top