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

com.akkaserverless.javasdk.impl.replicatedentity.ReplicatedEntitiesImpl.scala Maven / Gradle / Ivy

/*
 * Copyright 2021 Lightbend Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.akkaserverless.javasdk.impl.replicatedentity

import akka.NotUsed
import akka.actor.ActorSystem
import akka.stream.scaladsl.{ Flow, Source }
import com.akkaserverless.javasdk.impl._
import com.akkaserverless.javasdk.impl.effect.{ EffectSupport, ErrorReplyImpl, MessageReplyImpl }
import com.akkaserverless.javasdk.impl.replicatedentity.ReplicatedEntityEffectImpl.DeleteEntity
import com.akkaserverless.javasdk.impl.replicatedentity.ReplicatedEntityRouter.CommandResult
import com.akkaserverless.javasdk.replicatedentity._
import com.akkaserverless.javasdk.{ Context, Metadata }
import com.akkaserverless.protocol.entity.Command
import com.akkaserverless.protocol.replicated_entity.ReplicatedEntityStreamIn.{ Message => In }
import com.akkaserverless.protocol.replicated_entity.ReplicatedEntityStreamOut.{ Message => Out }
import com.akkaserverless.protocol.replicated_entity._
import com.google.protobuf.any.{ Any => ScalaPbAny }
import com.google.protobuf.Descriptors

import scala.util.control.NonFatal
import com.akkaserverless.javasdk.impl.ReplicatedEntityFactory
import com.akkaserverless.protocol.component.Failure
import org.slf4j.LoggerFactory

final class ReplicatedEntityService(
    val factory: ReplicatedEntityFactory,
    override val descriptor: Descriptors.ServiceDescriptor,
    val anySupport: AnySupport,
    override val entityType: String,
    val entityOptions: Option[ReplicatedEntityOptions])
    extends Service {

  def this(
      factory: ReplicatedEntityFactory,
      descriptor: Descriptors.ServiceDescriptor,
      anySupport: AnySupport,
      entityType: String,
      entityOptions: ReplicatedEntityOptions) = this(factory, descriptor, anySupport, entityType, Some(entityOptions))

  override final val componentType = ReplicatedEntities.name

  override def resolvedMethods: Option[Map[String, ResolvedServiceMethod[_, _]]] =
    factory match {
      case resolved: ResolvedEntityFactory => Some(resolved.resolvedMethods)
      case _                               => None
    }

  override def componentOptions: Option[ComponentOptions] = entityOptions
}

final class ReplicatedEntitiesImpl(system: ActorSystem, services: Map[String, ReplicatedEntityService])
    extends ReplicatedEntities {

  import ReplicatedEntitiesImpl._
  import EntityExceptions._

  private val log = LoggerFactory.getLogger(this.getClass)

  /**
   * After invoking handle, the first message sent will always be a ReplicatedEntityInit message, containing the entity
   * ID, and, if it exists or is available, the current state of the entity. After that, one or more commands may be
   * sent, as well as deltas as they arrive. The user function must respond with one reply per command in. They do not
   * necessarily have to be sent in the same order that the commands were sent, the command ID is used to correlate
   * commands to replies.
   */
  def handle(in: Source[ReplicatedEntityStreamIn, NotUsed]): Source[ReplicatedEntityStreamOut, NotUsed] =
    in.prefixAndTail(1)
      .flatMapConcat {
        case (Seq(ReplicatedEntityStreamIn(In.Init(init), _)), source) =>
          source.via(runEntity(init))
        case (Seq(), _) =>
          // if error during recovery in proxy the stream will be completed before init
          log.warn("Replicated Entity stream closed before init.")
          Source.empty[ReplicatedEntityStreamOut]
        case (Seq(ReplicatedEntityStreamIn(other, _)), _) =>
          throw ProtocolException(
            s"Expected init message for Replicated Entity, but received [${other.getClass.getName}]")
      }
      .recover { case error =>
        ErrorHandling.withCorrelationId { correlationId =>
          log.error(failureMessageForLog(error), error)
          ReplicatedEntityStreamOut(Out.Failure(Failure(description = s"Unexpected error [$correlationId]")))
        }
      }

  private def runEntity(
      init: ReplicatedEntityInit): Flow[ReplicatedEntityStreamIn, ReplicatedEntityStreamOut, NotUsed] = {
    val service =
      services.getOrElse(init.serviceName, throw ProtocolException(init, s"Service not found: ${init.serviceName}"))

    val initialData = init.delta.map { delta =>
      ReplicatedEntityDeltaTransformer.create(delta, service.anySupport)
    }

    val runner = new EntityRunner(service, init.entityId, initialData, system)

    Flow[ReplicatedEntityStreamIn]
      .mapConcat { in =>
        in.message match {
          case In.Command(command) =>
            List(runner.handleCommand(command))
          case In.Delta(delta) =>
            runner.handleDelta(delta)
            Nil
          case In.Delete(_) =>
            // ???
            Nil
          case In.Init(_) =>
            throw ProtocolException(init, "Replicated Entity received additional init message")
          case In.Empty =>
            throw ProtocolException(init, "Replicated Entity received empty or unknown message")
        }
      }
      .recover { case error =>
        ErrorHandling.withCorrelationId { correlationId =>
          LoggerFactory.getLogger(runner.handler.entityClass).error(failureMessageForLog(error), error)
          ReplicatedEntityStreamOut(Out.Failure(Failure(description = s"Unexpected error [$correlationId]")))
        }
      }
  }
}

object ReplicatedEntitiesImpl {
  import EntityExceptions._

  private class EntityRunner(
      service: ReplicatedEntityService,
      entityId: String,
      initialData: Option[InternalReplicatedData],
      system: ActorSystem) {

    val handler = {
      val context = new ReplicatedEntityCreationContext(entityId, system)
      try {
        service.factory.create(context)
      } finally {
        context.deactivate()
      }
    }

    handler._internalInitialData(initialData, service.anySupport)

    def handleDelta(delta: ReplicatedEntityDelta): Unit = {
      handler._internalApplyDelta(entityId, delta)
    }

    def handleCommand(command: Command): ReplicatedEntityStreamOut = {
      if (entityId != command.entityId)
        throw ProtocolException(command, "Entity is not the intended recipient of command")

      val context = new ReplicatedEntityCommandContext(entityId, command, system)
      val payload = command.payload.getOrElse(throw ProtocolException(command, "No command payload"))
      val cmd = service.anySupport.decodeMessage(payload)

      val CommandResult(effect: ReplicatedEntityEffectImpl[_, _]) =
        try {
          handler._internalHandleCommand(command.name, cmd, context)
        } catch {
          case e: EntityException => throw e
          case NonFatal(error)    => throw EntityException(command, s"Unexpected failure: $error", Some(error))
        } finally {
          context.deactivate()
        }

      val serializedSecondaryEffect = effect.secondaryEffect match {
        case MessageReplyImpl(message, metadata, sideEffects) =>
          MessageReplyImpl(service.anySupport.encodeJava(message), metadata, sideEffects)
        case other => other
      }

      val clientAction =
        serializedSecondaryEffect.replyToClientAction(service.anySupport, command.id, allowNoReply = false)

      serializedSecondaryEffect match {
        case error: ErrorReplyImpl[_] =>
          if (handler._internalHasDelta)
            throw EntityException(command, s"Replicated entity was changed for a failed command, this is not allowed.")
          ReplicatedEntityStreamOut(
            ReplicatedEntityStreamOut.Message.Reply(
              ReplicatedEntityReply(commandId = command.id, clientAction = clientAction)))

        case _ => // non-error
          val stateAction: Option[ReplicatedEntityStateAction] = effect.primaryEffect match {
            case DeleteEntity =>
              Some(ReplicatedEntityStateAction(ReplicatedEntityStateAction.Action.Delete(ReplicatedEntityDelete())))
            case _ =>
              if (handler._internalHasDelta) {
                val delta = handler._internalGetAndResetDelta
                Some(
                  ReplicatedEntityStateAction(ReplicatedEntityStateAction.Action.Update(ReplicatedEntityDelta(delta))))
              } else {
                None
              }
          }
          ReplicatedEntityStreamOut(
            ReplicatedEntityStreamOut.Message.Reply(
              ReplicatedEntityReply(
                command.id,
                clientAction,
                EffectSupport.sideEffectsFrom(service.anySupport, serializedSecondaryEffect),
                stateAction)))
      }
    }
  }

  private final class ReplicatedEntityCreationContext(override val entityId: String, system: ActorSystem)
      extends AbstractContext(system)
      with ReplicatedEntityContext
      with ActivatableContext

  private final class ReplicatedEntityCommandContext(
      override val entityId: String,
      command: Command,
      system: ActorSystem)
      extends AbstractContext(system)
      with CommandContext
      with ActivatableContext {

    override val commandId: Long = command.id

    override val commandName: String = command.name

    override val metadata: Metadata = new MetadataImpl(command.metadata.map(_.entries.toVector).getOrElse(Nil))

  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy