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

kalix.javasdk.impl.eventsourcedentity.EventSourcedHandlersExtractor.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 kalix.javasdk.impl.eventsourcedentity

import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import kalix.javasdk.annotations.EventHandler
import kalix.javasdk.impl.MethodInvoker
import kalix.javasdk.impl.JsonMessageCodec
import kalix.javasdk.impl.reflection.ParameterExtractors

object EventSourcedHandlersExtractor {
  def handlersFrom(entityClass: Class[_], messageCodec: JsonMessageCodec): EventSourceEntityHandlers = {

    val annotatedHandlers = entityClass.getDeclaredMethods
      .filter(_.getAnnotation(classOf[EventHandler]) != null)
      .toList

    val genericTypeArguments = entityClass.getGenericSuperclass
      .asInstanceOf[ParameterizedType]
      .getActualTypeArguments
    // the type parameter from the entity defines the return type of each event handler
    val returnType = genericTypeArguments.head
      .asInstanceOf[Class[_]]

    val eventType = genericTypeArguments(1).asInstanceOf[Class[_]]

    val (invalidHandlers, validSignatureHandlers) = annotatedHandlers.partition((m: Method) =>
      m.getParameterCount != 1 || !Modifier.isPublic(m.getModifiers) || (returnType != m.getReturnType))

    def eventTypeExtractor: Method => Class[_] = (mt: Method) => mt.getParameterTypes.head
    val eventTypeGrouped = validSignatureHandlers
      .groupBy(eventTypeExtractor)

    val (duplicatedEventTypes, validHandlers) = eventTypeGrouped.partition(_._2.length > 1)

    val errorsForSignatures =
      if (invalidHandlers.isEmpty) List.empty
      else
        List(
          HandlerValidationError(
            invalidHandlers,
            "must be public, with exactly one parameter and return type '" + returnType.getTypeName + "'"))

    val errorsForDuplicates =
      for (elem <- duplicatedEventTypes)
        yield HandlerValidationError(
          elem._2,
          "cannot have duplicate event handlers for the same event type: '" + elem._1.getName + "'")

    val missingEventHandler =
      if (eventType.isSealed) {
        val missingHandlerClasses = eventType.getPermittedSubclasses
          .filterNot(validHandlers.contains)
          .toList
        if (missingHandlerClasses.isEmpty) {
          List.empty
        } else {
          List(HandlerValidationError(List.empty, "missing event handler", missingHandlerClasses))
        }
      } else {
        List.empty
      }

    EventSourceEntityHandlers(
      handlers = validHandlers.flatMap { case (classType, methods) =>
        val invoker = MethodInvoker(methods.head, ParameterExtractors.AnyBodyExtractor[AnyRef](classType))
        //in case of schema evolution more types can point to the same invoker
        messageCodec.typeUrlsFor(classType).map(typeUrl => typeUrl -> invoker)
      },
      errors = errorsForSignatures ++ errorsForDuplicates.toList ++ missingEventHandler)
  }
}

private[kalix] final case class EventSourceEntityHandlers private (
    handlers: Map[String, MethodInvoker],
    errors: List[HandlerValidationError])

private[kalix] final case class HandlerValidationError(
    methods: List[Method],
    description: String,
    missingHandlersFor: List[Class[_]] = List.empty) {
  override def toString: String =
    s"ValidationError(reason='$description', offendingMethods=${methods.map(
      _.getName)}, missingHandlersFor=${missingHandlersFor.map(_.getName)}"
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy