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

io.axoniq.inspector.client.SetupPayloadCreator.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2022-2023. Inspector Axon
 *
 * 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 io.axoniq.inspector.client

import io.axoniq.inspector.api.*
import io.axoniq.inspector.unwrapPossiblyDecoratedClass
import org.axonframework.commandhandling.CommandBus
import org.axonframework.common.ReflectionUtils
import org.axonframework.config.Configuration
import org.axonframework.config.EventProcessingModule
import org.axonframework.eventhandling.MultiStreamableMessageSource
import org.axonframework.eventhandling.StreamingEventProcessor
import org.axonframework.eventhandling.tokenstore.TokenStore
import org.axonframework.eventsourcing.eventstore.EventStore
import org.axonframework.messaging.StreamableMessageSource
import org.axonframework.queryhandling.QueryBus
import org.axonframework.serialization.upcasting.Upcaster
import org.axonframework.util.MavenArtifactVersionResolver
import java.time.temporal.ChronoUnit
import java.time.temporal.TemporalAmount

class SetupPayloadCreator(
    private val configuration: Configuration,
    private val eventProcessingConfiguration: EventProcessingModule = configuration.eventProcessingConfiguration() as EventProcessingModule,
) {

    fun createReport(): SetupPayload {
        val processors = eventProcessingConfiguration.eventProcessors()
            .filter { it.value is StreamingEventProcessor }
            .map { entry ->
                entry.key
            }
        return SetupPayload(
            commandBus = commandBusInformation(),
            queryBus = queryBusInformation(),
            eventStore = eventBusInformation(),
            processors = processors.map {
                val processor =
                    eventProcessingConfiguration.eventProcessor(it, StreamingEventProcessor::class.java).get()
                ProcessorInformation(
                    name = it,
                    supportsReset = processor.supportsReset(),
                    batchSize = processor.getBatchSize(),
                    messageSourceType = processor.getMessageSource(),
                    tokenClaimInterval = processor.getTokenClaimInterval(),
                    tokenStoreClaimTimeout = processor.getStoreTokenClaimTimeout(),
                    errorHandler = eventProcessingConfiguration.errorHandler(it)::class.java.name,
                    invocationErrorHandler = eventProcessingConfiguration.listenerInvocationErrorHandler(it)::class.java.name,
                    interceptors = processor.getInterceptors("interceptors"),
                    tokenStoreType = processor.getStoreTokenStoreType(),
                    contexts = processor.contexts()
                )
            },
            versions = versionInformation(),
            upcasters = upcasters()
        )
    }

    private fun upcasters(): List {
        val upcasters =
            configuration.upcasterChain().getPropertyValue>>("upcasters") ?: emptyList()
        return upcasters.map { it::class.java.name }
    }

    private val dependenciesToCheck = listOf(
        "org.axonframework:axon-messaging",
        "org.axonframework:axon-configuration",
        "org.axonframework:axon-disruptor",
        "org.axonframework:axon-eventsourcing",
        "org.axonframework:axon-legacy",
        "org.axonframework:axon-metrics",
        "org.axonframework:axon-micrometer",
        "org.axonframework:axon-modelling",
        "org.axonframework:axon-server-connector",
        "org.axonframework:axon-spring",
        "org.axonframework:axon-spring-boot-autoconfigure",
        "org.axonframework:axon-spring-boot-starter",
        "org.axonframework:axon-tracing-opentelemetry",
        "org.axonframework:axon-tracing-amqp",
        "org.axonframework.extensions.amqp:axon-amqp",
        "org.axonframework.extensions.jgroups:axon-jgroups",
        "org.axonframework.extensions.kafka:axon-kafka",
        "org.axonframework.extensions.mongo:axon-mongo",
        "org.axonframework.extensions.reactor:axon-reactor",
        "org.axonframework.extensions.springcloud:axon-springcloud",
        "org.axonframework.extensions.tracing:axon-tracing",
        "io.axoniq:axonserver-connector-java",
        "io.axoniq.inspector:inspector-axon",
    )

    private fun versionInformation(): Versions {
        return Versions(
            frameworkVersion = resolveVersion("org.axonframework:axon-messaging") ?: "Unknown",
            moduleVersions = dependenciesToCheck.map { ModuleVersion(it, resolveVersion(it)) }
        )
    }

    private fun resolveVersion(dep: String): String? {
        val (groupId, artifactId) = dep.split(":")
        return MavenArtifactVersionResolver(
            groupId,
            artifactId,
            this::class.java.classLoader
        ).get()
    }

    private fun queryBusInformation(): QueryBusInformation {
        val bus = configuration.queryBus().unwrapPossiblyDecoratedClass(QueryBus::class.java)
        val axonServer = bus::class.java.name == "org.axonframework.axonserver.connector.query.AxonServerQueryBus"
        val localSegmentType = if (axonServer) bus.getPropertyTypeNested("localSegment", QueryBus::class.java) else null
        val context = if (axonServer) bus.getPropertyValue("context") else null
        val handlerInterceptors = if (axonServer) {
            bus.getPropertyValue("localSegment")?.getInterceptors("handlerInterceptors") ?: emptyList()
        } else {
            bus.getInterceptors("handlerInterceptors")
        }
        val dispatchInterceptors = bus.getInterceptors("dispatchInterceptors")
        val messageSerializer = if (axonServer) {
            bus.getPropertyValue("serializer")?.getSerializerType("messageSerializer")
        } else null
        val serializer = if (axonServer) {
            bus.getPropertyValue("serializer")?.getSerializerType("serializer")
        } else null
        return QueryBusInformation(
            type = bus::class.java.name,
            axonServer = axonServer,
            localSegmentType = localSegmentType,
            context = context,
            handlerInterceptors = handlerInterceptors,
            dispatchInterceptors = dispatchInterceptors,
            messageSerializer = messageSerializer,
            serializer = serializer,
        )
    }

    private fun eventBusInformation(): EventStoreInformation {
        val bus = configuration.eventBus().unwrapPossiblyDecoratedClass(EventStore::class.java)
        val axonServer =
            bus::class.java.name == "org.axonframework.axonserver.connector.event.axon.AxonServerEventStore"
        val context = if (axonServer) {
            bus.getPropertyValue("storageEngine")?.getPropertyValue("context")
        } else null
        val dispatchInterceptors = bus.getInterceptors("dispatchInterceptors")
        return EventStoreInformation(
            type = bus::class.java.name,
            axonServer = axonServer,
            context = context,
            dispatchInterceptors = dispatchInterceptors,
            eventSerializer = bus.getPropertyValue("storageEngine")?.getSerializerType("eventSerializer"),
            snapshotSerializer = bus.getPropertyValue("storageEngine")?.getSerializerType("snapshotSerializer"),
        )
    }

    private fun commandBusInformation(): CommandBusInformation {
        val bus = configuration.commandBus().unwrapPossiblyDecoratedClass(CommandBus::class.java)
        val axonServer = bus::class.java.name == "org.axonframework.axonserver.connector.command.AxonServerCommandBus"
        val localSegmentType = if (axonServer) bus.getPropertyTypeNested("localSegment", CommandBus::class.java) else null
        val context = if (axonServer) bus.getPropertyValue("context") else null
        val handlerInterceptors = if (axonServer) {
            bus.getPropertyValue("localSegment")?.getInterceptors("handlerInterceptors", "invokerInterceptors")
                ?: emptyList()
        } else {
            bus.getInterceptors("handlerInterceptors", "invokerInterceptors")
        }
        val dispatchInterceptors = bus.getInterceptors("dispatchInterceptors")
        val serializer = if (axonServer) {
            bus.getPropertyValue("serializer")?.getSerializerType("messageSerializer")
        } else null
        return CommandBusInformation(
            type = bus::class.java.name,
            axonServer = axonServer,
            localSegmentType = localSegmentType,
            context = context,
            handlerInterceptors = handlerInterceptors,
            dispatchInterceptors = dispatchInterceptors,
            messageSerializer = serializer
        )
    }

    private fun  Any.getPropertyValue(fieldName: String): T? {
        val field = ReflectionUtils.fieldsOf(this::class.java).firstOrNull { it.name == fieldName } ?: return null
        return ReflectionUtils.getMemberValue(
            field,
            this
        )
    }

    private fun Any.getPropertyType(fieldName: String): String {
        return ReflectionUtils.getMemberValue(
            ReflectionUtils.fieldsOf(this::class.java).first { it.name == fieldName },
            this
        ).let { it::class.java.name }
    }

    private fun Any.getPropertyTypeNested(fieldName: String, clazz: Class): String {
        return ReflectionUtils.getMemberValue(
            ReflectionUtils.fieldsOf(this::class.java).first { it.name == fieldName },
            this
        )
            .let { it.unwrapPossiblyDecoratedClass(clazz) }
            .let { it::class.java.name }
    }

    private fun StreamingEventProcessor.getBatchSize(): Int = getPropertyValue("batchSize") ?: -1
    private fun StreamingEventProcessor.getMessageSource(): String = getPropertyTypeNested("messageSource", EventStore::class.java)
    private fun StreamingEventProcessor.getTokenClaimInterval(): Long = getPropertyValue("tokenClaimInterval") ?: -1
    private fun StreamingEventProcessor.getStoreTokenStoreType(): String = getPropertyTypeNested("tokenStore", TokenStore::class.java)
    private fun StreamingEventProcessor.getStoreTokenClaimTimeout(): Long = getPropertyValue("tokenStore")
        ?.getPropertyValue("claimTimeout")?.let { it.get(ChronoUnit.SECONDS) * 1000 } ?: -1


    private fun Any.getInterceptors(vararg fieldNames: String): List {

        val interceptors = fieldNames.firstNotNullOfOrNull { this.getPropertyValue(it) } ?: return emptyList()
        if (interceptors::class.java.name == "org.axonframework.axonserver.connector.DispatchInterceptors") {
            return interceptors.getInterceptors("dispatchInterceptors")
        }
        if (interceptors !is List<*>) {
            return emptyList()
        }
        return interceptors
            .filterNotNull()
            .map {
                if (it is InspectorMeasuringHandlerInterceptor) {
                    InterceptorInformation(it.subject::class.java.name, true)
                } else InterceptorInformation(it::class.java.name, false)
            }
            .filter { !it.type.startsWith("org.axonframework.eventhandling") }
    }

    private fun Any.getSerializerType(fieldName: String): SerializerInformation? {
        val serializer = getPropertyValue(fieldName) ?: return null
        if (serializer::class.java.name == "org.axonframework.axonserver.connector.event.axon.GrpcMetaDataAwareSerializer") {
            return SerializerInformation(serializer.getPropertyType("delegate"), true)
        }
        return SerializerInformation(serializer::class.java.name, false)
    }

    private fun StreamingEventProcessor.contexts(): List {
        val messageSource = getPropertyValue>("messageSource") ?: return emptyList()
        val sources = if (messageSource is MultiStreamableMessageSource) {
            messageSource.getPropertyValue>>("eventStreams") ?: emptyList()
        } else {
            listOf(messageSource)
        }
        return sources.mapNotNull { toContext(it) }.distinct()
    }

    private fun toContext(it: StreamableMessageSource<*>): String? {
        if (it::class.java.simpleName == "AxonServerEventStore") {
            return it.getPropertyValue("storageEngine")?.getPropertyValue("context")
        }
        if (it::class.java.simpleName == "AxonIQEventStorageEngine") {
            return it.getPropertyValue("context")
        }
        // Fallback
        return it.getPropertyValue("context")
    }

}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy