All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.scalatra.commands.executors.scala Maven / Gradle / Ivy

package org.scalatra.commands

import scalaz._
import syntax.validation._
import akka.dispatch.{Promise, ExecutionContext, Future}
import org.scalatra.validation._
import grizzled.slf4j.Logger
import scala.util.control.Exception.allCatch
import mojolly.inflector.InflectorImports._
import annotation.implicitNotFound

trait CommandExecutors {

  implicit def syncExecutor[T <: Command, S](handler: T => ModelValidation[S]): CommandExecutor[T, ModelValidation[S]] =
    new BlockingCommandExecutor(handler)

  implicit def syncModelExecutor[T <: Command <% S, S](handler: S => ModelValidation[S]): CommandExecutor[T, ModelValidation[S]] =
    new BlockingModelExecutor[T, S](handler)

  implicit def asyncExecutor[T <: Command, S](handler: T => Future[ModelValidation[S]])(implicit executionContext: ExecutionContext): CommandExecutor[T, Future[ModelValidation[S]]] =
    new AsyncCommandExecutor(handler)

  implicit def asyncModelExecutor[T <: Command, S](handler: S => Future[ModelValidation[S]])(implicit executionContext: ExecutionContext, vw: T => S): CommandExecutor[T, Future[ModelValidation[S]]] =
    new AsyncModelExecutor[T, S](handler)

}

object CommandExecutors extends CommandExecutors



/**
 * A typeclass for executing commands. This allows for picking an executor based on the return type of the
 * method that is used to handle the command.
 *
 * @param handler The function to handle the command
 * @tparam T The command type
 * @tparam S The result type of executing the command
 */
@implicitNotFound(
  "Couldn't find an executor for command of type ${T} and result of type ${S}. Did you import org.scalatra.commands.CommandExecutors._ ? You can also implement your own org.scalatra.CommandExecutor."
)
abstract class CommandExecutor[T <: Command, S](handler: T => S) {
  def execute(command: T): S
}

abstract class BlockingExecutor[T <: Command, S](handle: T => ModelValidation[S]) extends CommandExecutor[T, ModelValidation[S]](handle) {
  @transient private[this] val logger = Logger(getClass)

  def execute(cmd: T): ModelValidation[S] = {
    logger.debug("Executing ["+cmd.getClass.getName+"].\n"+cmd)
    if (cmd.isValid) {
      val res = allCatch.either(handle(cmd))

      res match {
        case Right(r) ⇒
          val resultLog = r.fold(
            { failures ⇒ "with %d failures.\n%s".format(failures.tail.size + 1, failures.list) },
            { _ ⇒ "successfully." })
          logger.debug("Command ["+cmd.getClass.getName+"] executed "+resultLog)
          r
        case Left(t) ⇒
          logger.error("Command ["+cmd.getClass.getName+"] failed.", t)
          ValidationError("Failed to execute "+cmd.getClass.getSimpleName.underscore.humanize, UnknownError).failNel[S]
      }
    } else {
      val f = cmd.errors.map(_.validation) collect { case Failure(e) ⇒ e }
      def failures = if (f.size == 1) "failure".singularize else "failure".pluralize
      logger.debug("Command ["+cmd.getClass.getName+" executed with "+f.size+" "+failures+".\n"+f.toList)
      NonEmptyList(f.head, f.tail: _*).fail
    }
  }
}

/**
 * A command executor that can potentially block while executing the command
 *
 * @see [[org.scalatra.commands.CommandExecutor]]
 *
 * @param handle The function that takes a command and returns a [[org.scalatra.commands.ModelValidation]]
 * @tparam T The type of command to handle
 * @tparam S The success result type
 */
class BlockingCommandExecutor[T <: Command, S](handle: T => ModelValidation[S]) extends BlockingExecutor(handle)

/**
 * A command executor that can potentially block while executing the command, uses a model as input value for the handler function.
 * This command requires an implicit conversion from command to model to be in scope ie.: by defining it in the companion
 * object of the command.
 *
 * @see [[org.scalatra.commands.CommandExecutor]]
 *
 * @param handle The function that takes a model and returns a [[org.scalatra.commands.ModelValidation]],
 *               requires an implicit conversion from command to model to be in scope
 * @tparam T The type of model
 * @tparam S The success result type
 */
class BlockingModelExecutor[T <: Command <% S, S](handle: S => ModelValidation[S]) extends BlockingExecutor[T, S](handle(_))

abstract class AsyncExecutor[T <: Command, S](handle: T => Future[ModelValidation[S]])(implicit executionContext: ExecutionContext) extends CommandExecutor[T, Future[ModelValidation[S]]](handle) {
  @transient private[this] val logger = Logger(getClass)
  def execute(cmd: T): Future[ModelValidation[S]] = {
    logger.debug("Executing ["+cmd.getClass.getName+"].\n"+cmd)
    if (cmd.isValid) {
      val res = handle(cmd)

      res onSuccess {
        case r ⇒
          val resultLog = r.fold(
            { failures ⇒ "with %d failures.\n%s".format(failures.tail.size + 1, failures.list) },
            { _ ⇒ "successfully." })
          logger.debug("Command ["+cmd.getClass.getName+"] executed "+resultLog)
      }

      res recover {
        case t: Throwable =>
          logger.error("Command ["+cmd.getClass.getName+"] failed.", t)
          ValidationError("Failed to execute "+cmd.getClass.getSimpleName.underscore.humanize, UnknownError).failNel[S]
      }
    } else {
      val f = cmd.errors.map(_.validation) collect { case Failure(e) ⇒ e }
      def failures = if (f.size == 1) "failure".singularize else "failure".pluralize
      logger.debug("Command ["+cmd.getClass.getName+" executed with "+f.size+" "+failures+".\n"+f.toList)
      Promise.successful(NonEmptyList(f.head, f.tail: _*).fail).future
    }
  }
}
/**
 * A command executor that doesn't block while executing the command
 *
 * @see [[org.scalatra.commands.CommandExecutor]]
 *
 * @param handle The function that takes a command and returns a Future.
 * @tparam T The type of command to handle
 * @tparam S The success result type
 */
class AsyncCommandExecutor[T <: Command, S](handle: T => Future[ModelValidation[S]])(implicit executionContext: ExecutionContext) extends AsyncExecutor[T, S](handle)(executionContext)
/**
 * A command executor that can potentially block while executing the command, uses a model as input value for the handler function.
 * This command requires an implicit conversion from command to model to be in scope ie.: by defining it in the companion
 * object of the command.
 *
 * @see [[org.scalatra.commands.CommandExecutor]]
 *
 * @param handle The function that takes a model and returns a Future,
 *               requires an implicit conversion from command to model to be in scope
 * @tparam T The type of model
 * @tparam S The success result type
 */
class AsyncModelExecutor[T <: Command, S](handle: S => Future[ModelValidation[S]])(implicit executionContext: ExecutionContext, vw: T => S) extends AsyncExecutor[T, S](handle(_))(executionContext)




© 2015 - 2025 Weber Informatics LLC | Privacy Policy