Query Graphql is not called - spring-boot

After migrating a kotlin project's spring boot version to 2.7.5, I needed to change some old dependencies.
After these updates, neither queries nor mutations are being called.
As a result, the graphql call returns null.
The application starts up normally, with no errors. And no error is logged when I call graphql through postman.
Below is my Resolver class
package io.company.op.vehicle.resolver
import io.company.op.vehicle.model.\*
import io.company.op.vehicle.model.dto.ModelConfigInput
import io.company.op.vehicle.pagination.ModelConfigPage
import io.company.op.vehicle.security.SecurityService
import io.company.op.vehicle.service.ModelConfigService
import io.company.op.vehicle.utils.formattedError
import org.slf4j.LoggerFactory
import org.springframework.graphql.data.method.annotation.MutationMapping
import org.springframework.graphql.data.method.annotation.QueryMapping
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.stereotype.Component
#Component
class ModelConfigResolver(
private val modelConfigService: ModelConfigService,
private val securityService: SecurityService
) {
#PreAuthorize("#securityService.canFetchModelConfig()")
#QueryMapping
fun fetchAllModelConfig(
searchTerm: String,
searchEngineTypeEmpty: Boolean,
searchLockLocationTypeEmpty: Boolean,
searchTransmissionTypeEmpty: Boolean,
engineTypes: List<EngineType>,
lockLocationTypes: List<LockLocationType>,
transmissionTypes: List<TransmissionType>,
status: List<ModelConfigStatus>,
page: Int,
pageSize: Int = 5
): ModelConfigPage? {
val user = securityService.getUser()
try {
return modelConfigService.fetchAllModelConfig(
searchTerm,
searchEngineTypeEmpty,
searchLockLocationTypeEmpty,
searchTransmissionTypeEmpty,
engineTypes,
lockLocationTypes,
transmissionTypes,
status,
page,
pageSize
)
} catch (ex: Exception) {
log.formattedError(
"fetchAllModelConfig",
user,
"SearchTerm: $searchTerm, EngineTypes: $engineTypes, LockLocationTypes: $lockLocationTypes, TransmissionTypes: $transmissionTypes, Status: $status",
ex
)
throw ex
}
}
#PreAuthorize("#securityService.canFetchModelConfig()")
#QueryMapping
fun fetchModelConfig(
vehicleModelId: Long,
vds: String,
modelYear: Int,
transmissionType: TransmissionType
): ModelConfig? {
val user = securityService.getUser()
try {
return modelConfigService.fetchModelConfig(
vehicleModelId, vds, modelYear, transmissionType
)
} catch (ex: Exception) {
log.formattedError(
"fetchModelConfig",
user,
"VehicleModelId: $vehicleModelId, vds: $vds, modelYear: $modelYear, TransmissionType: $transmissionType",
ex
)
throw ex
}
}
#PreAuthorize("#securityService.canFetchModelConfig()")
#QueryMapping
fun fetchModelConfigById(
id: Long
): ModelConfig? {
val user = securityService.getUser()
try {
return modelConfigService.fetchModelConfigById(id)
} catch (ex: Exception) {
log.formattedError(
"fetchModelConfig",
user,
"Id: $id",
ex
)
throw ex
}
}
#PreAuthorize("#securityService.canCreateOrUpdateModelConfig()")
#MutationMapping
fun createOrUpdateModelConfig(modelConfigInput: ModelConfigInput): ModelConfig {
val user = securityService.getUser()
try {
return modelConfigService.createOrUpdateModelConfig(modelConfigInput, user)
} catch (ex: Exception) {
log.formattedError(
"createOrUpdateModelConfig",
user,
"ModelConfigInput: $modelConfigInput",
ex
)
throw ex
}
}
#PreAuthorize("#securityService.canHomologateModelConfig()")
#MutationMapping
fun homologateModelConfig(modelConfigInput: ModelConfigInput): ModelConfig {
val user = securityService.getUser()
try {
return modelConfigService.homologateModelConfig(modelConfigInput, user)
} catch (ex: Exception) {
log.formattedError(
"homologateModelConfig",
user,
"ModelConfigInput: $modelConfigInput",
ex
)
throw ex
}
}
#PreAuthorize("#securityService.canDeleteModelConfig()")
#MutationMapping
fun deleteModelConfig(id: Long): ModelConfig {
val user = securityService.getUser()
try {
return modelConfigService.deleteModelConfig(id, user)
} catch (ex: Exception) {
log.formattedError(
"deleteModelConfig",
user,
"ModelConfigId: $id",
ex
)
throw ex
}
}
companion object {
val log = LoggerFactory.getLogger(ModelConfigResolver::class.java)!!
}
}
SecurityConfiguration Class
package io.company.op.vehicle.security
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import io.company.cd.common.spring.auth.AuthFilter
import io.company.cd.common.spring.auth.domain.TokenType
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.authentication.preauth.RequestHeaderAuthenticationFilter
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
class SecurityConfiguration : WebSecurityConfigurerAdapter() {
#Value("\${vehicle.op.jwk.key-set}")
private lateinit var keySet: List<String>
#Value("\${vehicle.op.jwt.token-types}")
private lateinit var jsonTokenTypes: String
protected val mapper = jacksonObjectMapper()
override fun configure(http: HttpSecurity) {
val tokenTypes: List<TokenType> = mapper.readValue(jsonTokenTypes, Array<TokenType>::class.java).toList()
http.csrf().disable()
.authorizeRequests()
.antMatchers("/graphql").permitAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(
AuthFilter.create(keySet, tokenTypes),
RequestHeaderAuthenticationFilter::class.java
)
}
}
SecurityService Class
package io.company.op.vehicle.security
import io.compaompanyny.cd.common.spring.auth.domain.IntegrationToken
import io.company.cd.common.spring.auth.domain.CToken
import io.company.cd.common.spring.auth.domain.UnrestrictedToken
import io.company.op.vehicle.exception.VehicleErrorType
import io.company.op.vehicle.exception.VehicleException
import org.slf4j.LoggerFactory
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.stereotype.Service
#Service
class SecurityService {
fun getUser(): User {
return when (val token = SecurityContextHolder.getContext().authentication.principal) {
is CompanyToken ->
User(
username = token.username,
type = UserType.COMPANY
)
is UnrestrictedToken ->
User(
username = "Unrestricted",
type = UserType.UNRESTRICTED
)
is IntegrationToken ->
User(
username = "Integration",
type = UserType.INTEGRATION
)
else -> throw VehicleException(VehicleErrorType.UNAUTHORIZED)
}
}
fun canFetchModelConfig(): Boolean {
val user = getUser()
log.debug("Validating fetch model config for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
fun canCreateOrUpdateModelConfig(): Boolean {
val user = getUser()
log.debug("Validating create model config for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
fun canDeleteModelConfig(): Boolean {
val user = getUser()
log.debug("Validating delete model config for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
fun canHomologateModelConfig(): Boolean {
val user = getUser()
log.debug("Validating homologate model config for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
fun canFetchVehicleModel(): Boolean {
val user = getUser()
log.debug("Validating fetch vehicle model for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
fun canFetchVehicleBrand(): Boolean {
val user = getUser()
log.debug("Validating fetch vehicle brand for User: ${user.username}")
return when (user.type) {
UserType.COMPANY -> true
UserType.UNRESTRICTED -> true
UserType.INTEGRATION -> true
}
}
companion object {
val log = LoggerFactory.getLogger(SecurityService::class.java)!!
}
}
Application Log
2022-12-08 18:17:42.748 INFO 8516 --- \[ main\] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo \[name: default\]
2022-12-08 18:17:42.810 INFO 8516 --- \[ main\] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.12.Final
2022-12-08 18:17:43.005 INFO 8516 --- \[ main\] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-12-08 18:17:43.152 INFO 8516 --- \[ main\] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.PostgreSQL9Dialect
2022-12-08 18:17:43.580 INFO 8516 --- \[ main\] o.h.e.t.j.p.i.JtaPlatformInitiator : HHH000490: Using JtaPlatform implementation: \[org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform\]
2022-12-08 18:17:43.592 INFO 8516 --- \[ main\] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-12-08 18:17:43.883 WARN 8516 --- \[ main\] .s.s.UserDetailsServiceAutoConfiguration :
Using generated security password: 96d94b82-7cd1-431c-a831-3ed6d56519bc
This generated password is for development use only. Your security configuration must be updated before running your application in production.
2022-12-08 18:17:44.091 INFO 8516 --- \[ main\] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with \[org.springframework.security.web.session.DisableEncodeUrlFilter#32db94fb, org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#6b61a4b0, org.springframework.security.web.context.SecurityContextPersistenceFilter#570089c4, org.springframework.security.web.header.HeaderWriterFilter#40e90634, org.springframework.security.web.authentication.logout.LogoutFilter#34538ffe, io.company.cd.common.spring.auth.AuthFilter#5c00de0d, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#6e818345, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#6e2e11ee, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#6278371a, org.springframework.security.web.session.SessionManagementFilter#b6fea12, org.springframework.security.web.access.ExceptionTranslationFilter#655d5285, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#64e680c5\]
2022-12-08 18:17:45.419 INFO 8516 --- \[ main\] s.b.a.g.s.GraphQlWebMvcAutoConfiguration : GraphQL endpoint HTTP POST /graphql
2022-12-08 18:17:45.981 INFO 8516 --- \[ main\] o.s.b.a.e.web.EndpointLinksResolver : Exposing 1 endpoint(s) beneath base path '/actuator'
2022-12-08 18:17:46.077 INFO 8516 --- \[ main\] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9090 (http) with context path ''
2022-12-08 18:17:46.094 INFO 8516 --- \[ main\] io.company.op.vehicle.VehicleApplicationKt : Started VehicleApplicationKt in 8.186 seconds (JVM running for 9.011)
2022-12-08 18:17:58.028 INFO 8516 --- \[nio-9090-exec-2\] o.a.c.c.C.\[Tomcat\].\[localhost\].\[/\] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-12-08 18:17:58.029 INFO 8516 --- \[nio-9090-exec-2\] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-12-08 18:17:58.031 INFO 8516 --- \[nio-9090-exec-2\] o.s.web.servlet.DispatcherServlet : Completed initialization in 2 ms
When calling the query fetchAllModelConfig it returns this:
{
"data": {
"fetchAllModelConfig": null
}
}

Related

Kafka disconnection with log Node 1 disconnected

When using kafka to communicate between services, I get the following logs. Then the events are not received by consumer or producer is not able to sends events:
2023-02-01 03:42:40.614 INFO 3524 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient : [Producer clientId=producer-1] Node 1 disconnected.
2023-02-01 03:47:41.228 INFO 3524 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient : [Producer clientId=producer-1] Node 0 disconnected.
2023-02-01 03:47:41.228 INFO 3524 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient : [Producer clientId=producer-1] Cancelled in-flight METADATA request with correlation id 617 due to node 0 being disconnected (elapsed time since creation: 108ms, elapsed time since send: 108ms, request timeout: 2000ms)
2023-02-01 05:02:48.399 INFO 3524 --- [ad | producer-1] org.apache.kafka.clients.NetworkClient : [Producer clientId=producer-1] Node 2 disconnected.
Configuration looks like this:
package com.bbh.ilab.aip.presentationservice.config
import com.bbh.ilab.aip.commons.kafka.BaseEvent
import com.bbh.ilab.aip.commons.kafka.EventPayload
import org.apache.kafka.clients.producer.ProducerConfig
import org.apache.kafka.common.config.SslConfigs
import org.apache.kafka.common.serialization.StringSerializer
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.kafka.core.DefaultKafkaProducerFactory
import org.springframework.kafka.core.KafkaTemplate
import org.springframework.kafka.core.ProducerFactory
import org.springframework.kafka.support.serializer.JsonSerializer
#Configuration
class KafkaProducerConfig(
private val environmentProperties: EnvironmentProperties
) {
#Bean
fun producerFactory(): ProducerFactory<String, BaseEvent<EventPayload>> {
val configProps = getStandardConfig()
if (environmentProperties.kafka.sslEnabled) {
addSslConfig(configProps)
}
return DefaultKafkaProducerFactory(configProps)
}
#Bean
fun kafkaTemplate(): KafkaTemplate<String, BaseEvent<EventPayload>> {
return KafkaTemplate(producerFactory())
}
private fun getStandardConfig(): MutableMap<String, Any> {
val configProps: MutableMap<String, Any> = HashMap()
configProps[ProducerConfig.BOOTSTRAP_SERVERS_CONFIG] = environmentProperties.kafka.bootstrapUrl
configProps[ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG] = StringSerializer::class.java
configProps[ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG] = JsonSerializer::class.java
configProps[ProducerConfig.DELIVERY_TIMEOUT_MS_CONFIG] = environmentProperties.kafka.retry.deliveryTimeoutMs
configProps[ProducerConfig.REQUEST_TIMEOUT_MS_CONFIG] = environmentProperties.kafka.retry.requestTimeoutMs
configProps[ProducerConfig.RETRY_BACKOFF_MS_CONFIG] = environmentProperties.kafka.retry.retryBackoffMs
return configProps
}
private fun addSslConfig(configProps: MutableMap<String, Any>) {
configProps["security.protocol"] = "SSL"
configProps[SslConfigs.SSL_KEYSTORE_TYPE_CONFIG] = "PKCS12"
configProps[SslConfigs.SSL_TRUSTSTORE_TYPE_CONFIG] = "PKCS12"
configProps[SslConfigs.SSL_KEYSTORE_LOCATION_CONFIG] = environmentProperties.kafka.keystorePath
configProps[SslConfigs.SSL_KEYSTORE_PASSWORD_CONFIG] = environmentProperties.kafka.keystorePass
configProps[SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG] = environmentProperties.kafka.truststorePath
configProps[SslConfigs.SSL_TRUSTSTORE_PASSWORD_CONFIG] = environmentProperties.kafka.truststorePass
configProps[SslConfigs.SSL_KEY_PASSWORD_CONFIG] = environmentProperties.kafka.keystorePass
}
}
Events are sent by this code:
fun sendSplitEvent(configDto: ConfigDto, domain: Domain, timestamp: LocalDateTime) {
val payload = SplitColumnRequestPayload(
configDto.projectId,
domain,
configDto.targetColumn,
configDto.split
)
val event = SplitRequestEvent(UUID.randomUUID(), timestamp, payload)
kafkaTemplate.send(environmentProperties.kafka.topic, event as BaseEvent<EventPayload>)
}
I tried to changing configuration following: https://github.com/strimzi/strimzi-kafka-operator/issues/2729 but still not working. Please help me :)

How to configure Spring Boot App to connect to Axon Server and register event handler

First, I have a spring boot application using axon 4.5.5 libraries connecting to server 4.2.4. Is this supported?
This spring boot app is suppose to listen to several events coming from the (main app emitted to the) axon server and here is my sprint boot axon client configuration in application.yml below.
Second, this application connects to the axon server but fails to handle any events causing all the said events to be blacklisted. I have trace it to the event handler registration is probably causing the issue. We are using the EventStore as the StreamableMessageSource when calling registerTrackingEventProcessor().
Do you guys have any ideas why the registered event handlers are not firing? I can see the spring boot app is connected to the axon server on the dashboard as well as the main app that fires the events. I can also see the fired events when searching it in the dashboard and their blacklisting in the axon server log. So my guess it is the configuration causing issues.
Here is my library versions (from pom.xml):
Version 4.5.5
axon-test
axon-spring-boot-autoconfigure
axon-spring-boot-starter
axon-modelling
axon-metrics
axon-messaging
axon-eventsourcing
Version 2.6.0
spring-boot-starter-web
Here is my application.yml axon fragment:
axon:
axonserver:
client-id: reporting-etl-client
component-name: reporting-etl
query-threads: ${AXON_QUERY_THREADS_MAX:50}
servers: ${AXON_AXONSERVER_SERVERS:axonserver}
serializer:
events: jackson
and AxonConfig.java:
package com.fedeee.reporting.config;
import com.fedeee.reporting.axon.events.EventHandlerProjector;
import com.thoughtworks.xstream.XStream;
import org.axonframework.axonserver.connector.query.AxonServerQueryBus;
import org.axonframework.config.EventProcessingConfigurer;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventhandling.TrackingEventProcessorConfiguration;
import org.axonframework.eventhandling.gateway.DefaultEventGateway;
import org.axonframework.eventhandling.gateway.EventGateway;
import org.axonframework.queryhandling.DefaultQueryGateway;
import org.axonframework.queryhandling.QueryBus;
import org.axonframework.queryhandling.QueryGateway;
import org.axonframework.serialization.AbstractXStreamSerializer;
import org.axonframework.serialization.Serializer;
import org.axonframework.serialization.xml.XStreamSerializer;
import org.reflections.Reflections;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.Assert;
import java.util.Set;
#Configuration
public class AxonConfig {
private static final Logger LOG = LoggerFactory.getLogger(AxonConfig.class);
/**
* Correctly configuring the XStream serializer to avoid security warnings.
*/
#Autowired
public void configureXStream(Serializer serializer) {
if (serializer instanceof AbstractXStreamSerializer) {
XStream xStream = ((XStreamSerializer) serializer).getXStream();
XStream.setupDefaultSecurity(xStream);
xStream.allowTypesByWildcard(new String[] {"com.fedeee.pkg.api.events.**", "org.axonframework.**"});
}
}
/**
*
* #param configurer
* #param context
*/
#Autowired
public void configure(EventProcessingConfigurer configurer, ApplicationContext context) {
LOG.info("Setting up TrackingEventProcessors for threads, batch size and other configurations..."
+ " annotated with #ProcessingGroup...");
// find classes in the com.fedeee.* package that has methods annotated with #ProcessingGroup to configure
Reflections reflections = new Reflections("com.fedeee.reporting.axon.events");
Set<Class<?>> annotatedClasses = reflections.getTypesAnnotatedWith(ProcessingGroup.class);
// Configure each identified class
annotatedClasses.stream().forEach(annotatedClass -> {
// Locate the appropriate spring bean to get appropriate values from each one.
String beanName = annotatedClass.getName().substring(annotatedClass.getName().lastIndexOf(".") + 1);
beanName = beanName.substring(0,1).toLowerCase() + beanName.substring(1);
Object projObj = context.getBean(beanName);
if (projObj instanceof EventHandlerProjector) {
EventHandlerProjector projector = (EventHandlerProjector) projObj;
LOG.info("Configuring EventHandlerProjector Bean '{}' with maxThreads: {} and batchSize: {}.",
beanName, projector.getMaxThreads(), projector.getBatchSize());
ProcessingGroup pgAnnotation = annotatedClass.getAnnotation(ProcessingGroup.class);
String processingGroup = pgAnnotation.value();
configurer.registerTrackingEventProcessor(
processingGroup,
org.axonframework.config.Configuration::eventStore,
conf -> TrackingEventProcessorConfiguration.forParallelProcessing(projector.getMaxThreads())
.andBatchSize(projector.getBatchSize())
).registerHandlerInterceptor(processingGroup, configuration -> new EventHandlerLoggingInterceptor());
// Enable logging for EventHandlers
LOG.info(".. '{}' successfully configured with processing group '{}'.", beanName, processingGroup);
} else {
LOG.info(".. '{}' failed to configure with any processing group.", beanName);
}
});
// TODO: handle tracking event processor initialization. See the axon mailing list thread:
// *****************************************************************************************************************
// https://groups.google.com/forum/#!topic/axonframework/eyw0rRiSzUw
// In that thread there is a discussion about properly initializing the token store to avoid recreating query models.
// I still need to understand more about this...
// *****************************************************************************************************************
}
// #Autowired
// public void configureErrorHandling(
// EventProcessingConfigurer configurer, ErrorHandler errorHandler
// ) {
// configurer.registerDefaultListenerInvocationErrorHandler(c -> errorHandler);
// }
#Autowired
public void registerInterceptors(QueryBus queryBus) {
Assert.notNull(queryBus, "Invalid configuration, queryBus is null!");
if (AxonServerQueryBus.class.isAssignableFrom(queryBus.getClass())) {
queryBus.registerHandlerInterceptor(InterceptorSupport.authorizationHandlerInterceptor());
}
}
#Bean
public QueryGateway queryGateway(QueryBus queryBus) {
return DefaultQueryGateway.builder().queryBus(queryBus).build();
}
#Bean
public EventGateway eventGateway(EventBus eventBus) {
return DefaultEventGateway.builder().eventBus(eventBus).build();
}
}
Here is the EventHandlerProjector.java:
package com.fedeee.reporting.axon.events;
/**
* Defines a contract to ensure we specify the number of threads and batch size to be allowed by the event
*/
public interface EventHandlerProjector {
/**
* Specifies the number of records per batch to be handled by the tracking processor.
* #return
*/
public default Integer getBatchSize() {
return 1;
}
/**
* Specifies the maximumn number of threads the tracking processor can be specified to use.
* #return
*/
public default Integer getMaxThreads() {
return 1;
}
}
And finally the event handler class:
package com.fedeee.reporting.axon.events.pkg;
import com.fedeee.api.UserInfo;
import com.fedeee.pkg.api.events.*;
import com.fedeee.reporting._shared.utils.MdcAutoClosable;
import com.fedeee.reporting.axon.events.EventHandlerProjector;
import com.fedeee.reporting.recover.packageevents.PackageCreatedDto;
import com.fedeee.reporting.recover.packageevents.RecoverPackageCreatedRequest;
import com.fedeee.reporting.recover.packageevents.RecoverPackageEditedDto;
import com.fedeee.reporting.recover.packageevents.RecoverPackageEventsService;
import com.fedeee.reporting.recover.translators.PackageEventTranslator;
import com.fedeee.reporting.recover.translators.UserInfoTranslator;
import com.fedeee.reporting.recover.user.RecoverUserDto;
import org.axonframework.config.ProcessingGroup;
import org.axonframework.eventhandling.EventHandler;
import org.axonframework.eventhandling.Timestamp;
import org.axonframework.messaging.annotation.MetaDataValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.Instant;
import static com.fedeee.api.AxonMessageMetadataKeys.USER_INFO;
/**
* Event TrackingProcessor for PackageRecord-based Events handled by Axon.
*
* Configurations can be provided with the given batchSize and maxThreads options via .env or docker-compose.
*
* IMPORTANT! <code>AxonConfig</code> looks for the <em>#ProcessingGroup</em> annotation to set everything up properly.
*/
#ProcessingGroup(value = "Package-Record")
#Component
public class PackageRecordProjector implements EventHandlerProjector {
#Value("${reporting-etl.tp-batch-size.package-record:1}")
private Integer batchSize;
#Value("${reporting-etl.tp-max-threads.package-record:5}")
private Integer maxThreads;
private RecoverPackageEventsService recoverPackageEventsService;
#Autowired
public PackageRecordProjector(RecoverPackageEventsService recoverPackageEventsService) {
super();
this.recoverPackageEventsService = recoverPackageEventsService;
}
private final Logger LOG = LoggerFactory.getLogger(this.getClass());
/**
* Event handler to handle packages created in Recover.
*
* This replaces the REST endpoint exposed and used by Recover in RecoverPackageEventsController.created().
*
* #param event
* #param occurrenceInstant
* #param userInfo
*/
#EventHandler
void on(PackageCreated event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
try (MdcAutoClosable mdc = new MdcAutoClosable()) {
mdcInit(event, userInfo, mdc);
LOG.info("Handling PackageCreated event...");
PackageCreatedDto createdDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);
RecoverUserDto recoverUserDto = UserInfoTranslator.from(userInfo);
RecoverPackageCreatedRequest request = new RecoverPackageCreatedRequest(createdDto, recoverUserDto);
/* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
disallow duplication via the REST endpoint. (There are comments in there already. :) )
*/
recoverPackageEventsService.save(request);
LOG.info("Finished handling PackageCreated event.");
} catch (Exception e) {
LOG.info("An Exception has been thrown : ", e);
throw e;
}
}
#EventHandler
void on(PackageTypeCorrected event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
try (MdcAutoClosable mdc = new MdcAutoClosable()) {
mdcInit(event, userInfo, mdc);
// TODO: not implemented (Recover and Reporting-ETL)
LOG.info("Finished handling PackageTypeCorrected event.");
} catch (Exception e) {
LOG.info("An Exception has been thrown : ", e);
throw e;
}
}
#EventHandler
void on(PackageDeleted event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
try (MdcAutoClosable mdc = new MdcAutoClosable()) {
mdcInit(event, userInfo, mdc);
// TODO: not implemented (Reporting-ETL)
LOG.info("Finished handling PackageDeleted event.");
} catch (Exception e) {
LOG.info("An Exception has been thrown : ", e);
throw e;
}
}
// TODO: not implemented (Recover and Reporting-ETL)
// #EventHandler
// void on(PackageIntegrated event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
// try (MdcAutoClosable mdc = new MdcAutoClosable()) {
// mdcInit(event, userInfo, mdc);
// } catch (Exception e) {
// LOG.info("An Exception has been thrown : ", e);
// throw e;
// }
// }
#EventHandler
void on(DataCaptureStarted event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
try (MdcAutoClosable mdc = new MdcAutoClosable()) {
mdcInit(event, userInfo, mdc);
RecoverPackageEditedDto editedDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);
/* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
disallow duplication via the REST endpoint. (There are comments in there already. :) )
*/
recoverPackageEventsService.save(editedDto);
LOG.info("Finished handling DataCaptureStarted event.");
} catch (Exception e) {
LOG.info("An Exception has been thrown : ", e);
throw e;
}
}
#EventHandler
void on(DataCaptureEnded event, #Timestamp Instant occurrenceInstant, #MetaDataValue(USER_INFO) UserInfo userInfo) {
try (MdcAutoClosable mdc = new MdcAutoClosable()) {
mdcInit(event, userInfo, mdc);
RecoverPackageEditedDto editedDto = PackageEventTranslator.from(event, occurrenceInstant, userInfo);
/* Once we are ready, comment this in and make appropriate changes to RecoverPackageEventsController to
disallow duplication via the REST endpoint. (There are comments in there already. :) )
*/
recoverPackageEventsService.update(event.getPackageId(), editedDto);
LOG.info("Finished handling DataCaptureEnded event.");
} catch (Exception e) {
LOG.info("An Exception has been thrown : ", e);
throw e;
}
}
#Override public Integer getBatchSize() {
return batchSize;
}
#Override public Integer getMaxThreads() {
return maxThreads;
}
private void mdcInit(PackageEvent event, UserInfo userInfo, MdcAutoClosable mdc) {
mdc.put("PackageId", event.getPackageId());
mdc.put("UserId", userInfo.getUserId());
LOG.info("Handling package record event: {}", event);
}
}
Here is the logs for today 2023/01/27...
.
.
.
2023-01-27 17:19:32.924 DEBUG 8 --- [MessageBroker-4] i.a.a.message.command.CommandCache : Checking timed out commands
2023-01-27 17:19:37.924 DEBUG 8 --- [MessageBroker-4] i.a.axonserver.message.query.QueryCache : Checking timed out queries
2023-01-27 17:19:37.924 DEBUG 8 --- [MessageBroker-3] i.a.a.message.command.CommandCache : Checking timed out commands
2023-01-27 17:19:40.299 DEBUG 8 --- [ool-5-thread-16] i.a.axonserver.grpc.PlatformService : Registered client : ClientComponent{client='reporting-etl-client', component='reporting-etl', context='default'}
2023-01-27 17:19:40.299 INFO 8 --- [ool-5-thread-16] i.a.a.logging.TopologyEventsLogger : Application connected: reporting-etl, clientId = reporting-etl-client, context = default
2023-01-27 17:19:40.299 DEBUG 8 --- [ool-5-thread-16] i.a.a.c.version.ClientVersionsCache : Version update received from client reporting-etl-client.default to version 4.4.
2023-01-27 17:19:40.332 INFO 8 --- [ool-5-thread-15] i.a.a.message.event.EventDispatcher : Starting tracking event processor for : - 209301
2023-01-27 17:19:42.925 DEBUG 8 --- [MessageBroker-7] i.a.axonserver.message.query.QueryCache : Checking timed out queries
2023-01-27 17:19:42.925 DEBUG 8 --- [MessageBroker-3] i.a.a.message.command.CommandCache : Checking timed out commands
.
.
.
2023-01-27 18:56:08.163 DEBUG 8 --- [MessageBroker-1] i.a.a.message.command.CommandCache : Checking timed out commands
2023-01-27 18:56:08.163 DEBUG 8 --- [MessageBroker-7] i.a.axonserver.message.query.QueryCache : Checking timed out queries
2023-01-27 18:56:09.242 DEBUG 8 --- [pool-5-thread-9] i.a.a.message.command.CommandDispatcher : Dispatch com.fedeee.pkg.api.commands.StartDataCapture to: recover-api-client.default
2023-01-27 18:56:09.257 DEBUG 8 --- [ool-5-thread-10] i.a.a.message.command.CommandDispatcher : Sending response to: io.axoniq.axonserver.message.command.CommandInformation#17b317ff
2023-01-27 18:56:09.294 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Send request io.axoniq.axonserver.grpc.SerializedQuery#158b1b9a, with priority: 0
2023-01-27 18:56:09.294 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Remaining time for message: 72152808-ca89-4565-82dd-2675e52686e2 - 3600000ms
2023-01-27 18:56:09.300 DEBUG 8 --- [pool-5-thread-5] i.a.axonserver.message.query.QueryCache : Remove messageId 72152808-ca89-4565-82dd-2675e52686e2
2023-01-27 18:56:09.300 DEBUG 8 --- [pool-5-thread-5] i.a.a.message.query.QueryDispatcher : No (more) information for 72152808-ca89-4565-82dd-2675e52686e2 on completed
2023-01-27 18:56:09.300 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Send request io.axoniq.axonserver.grpc.SerializedQuery#6e93a34b, with priority: 0
2023-01-27 18:56:09.301 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Remaining time for message: 53aa1974-4012-452a-b451-2957003e4b9f - 3599999ms
2023-01-27 18:56:09.306 DEBUG 8 --- [pool-5-thread-4] i.a.axonserver.message.query.QueryCache : Remove messageId 53aa1974-4012-452a-b451-2957003e4b9f
2023-01-27 18:56:09.306 DEBUG 8 --- [pool-5-thread-4] i.a.a.message.query.QueryDispatcher : No (more) information for 53aa1974-4012-452a-b451-2957003e4b9f on completed
2023-01-27 18:56:09.319 DEBUG 8 --- [ool-5-thread-13] i.a.a.m.q.s.handler.DirectUpdateHandler : SubscriptionQueryResponse for subscription Id 55748e24-e26f-4863-a5d0-a4eeff43da69 send to client.
2023-01-27 18:56:10.509 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Send request io.axoniq.axonserver.grpc.SerializedQuery#fd8d154, with priority: 0
2023-01-27 18:56:10.510 DEBUG 8 --- [st-dispatcher-2] i.a.a.grpc.GrpcQueryDispatcherListener : Remaining time for message: d2d91224-557f-4735-933b-e4195e7e42f9 - 3599999ms
2023-01-27 18:56:10.514 DEBUG 8 --- [ool-5-thread-15] i.a.axonserver.message.query.QueryCache : Remove messageId d2d91224-557f-4735-933b-e4195e7e42f9
2023-01-27 18:56:10.514 DEBUG 8 --- [ool-5-thread-15] i.a.a.message.query.QueryDispatcher : No (more) information for d2d91224-557f-4735-933b-e4195e7e42f9 on completed
2023-01-27 18:56:13.163 DEBUG 8 --- [MessageBroker-2] i.a.a.message.command.CommandCache : Checking timed out commands
2023-01-27 18:56:13.163 DEBUG 8 --- [MessageBroker-5] i.a.axonserver.message.query.QueryCache : Checking timed out queries
.
.
.
As you might be running into known bugs, it's better to use 4.6 of server and framework, that said, it should likely work since the server API is pretty stable.
Could you also share the com.fedeee.reporting.axon.events.EventHandlerProjector class? Has that class #EventHandler annotated methods? Are there more classes with #EventHandler annotated methods?
I assume you are using Spring Boot and the Axon Framework starter, is did indeed the case?

route FROM and route TO with spring cloud stream and functions

I have some issues with the new routing feature in spring cloud stream
I tried to implement a simple scenario, I want to send a message with a header spring.cloud.function.definition = consume1 or consume2
I expect that consume1 or consume2 should be called based on what is sent on the header but the methods are called randomly.
I send the message to the exchange consumer using the rabbit admin console
I'm having the following logs:
2020-02-27 14:48:25.896 INFO 22132 --- [ consumer.app-1] com.example.demo.TestConsumer : ==============>consume1 messge [[payload=ok, headers={amqp_receivedDeliveryMode=NON_PERSISTENT, amqp_receivedRoutingKey=#, amqp_receivedExchange=consumer, amqp_deliveryTag=1, deliveryAttempt=1, amqp_consumerQueue=consumer.app, amqp_redelivered=false, id=9a4dff25-88ef-4d76-93e2-c8719cda122d, spring.cloud.function.definition=consume1, amqp_consumerTag=amq.ctag-gGChFNCKIVd25yyR9H6-fQ, sourceData=(Body:'[B#3a92faa7(byte[2])' MessageProperties [headers={spring.cloud.function.definition=consume1}, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, redelivered=false, receivedExchange=consumer, receivedRoutingKey=#, deliveryTag=1, consumerTag=amq.ctag-gGChFNCKIVd25yyR9H6-fQ, consumerQueue=consumer.app]), timestamp=1582811303347}]]
2020-02-27 14:48:25.984 INFO 22132 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2020-02-27 14:48:25.984 INFO 22132 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2020-02-27 14:48:25.991 INFO 22132 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 7 ms
2020-02-27 14:48:26.037 INFO 22132 --- [oundedElastic-1] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel customer-1
2020-02-27 14:48:26.111 INFO 22132 --- [oundedElastic-1] o.s.c.s.m.DirectWithAttributesChannel : Channel 'application.customer-1' has 1 subscriber(s).
2020-02-27 14:48:26.116 INFO 22132 --- [oundedElastic-1] o.s.a.r.c.CachingConnectionFactory : Attempting to connect to: [localhost:5672]
2020-02-27 14:48:26.123 INFO 22132 --- [oundedElastic-1] o.s.a.r.c.CachingConnectionFactory : Created new connection: rabbitConnectionFactory.publisher#32438e24:0/SimpleConnection#3e58666d [delegate=amqp://guest#127.0.0.1:5672/, localPort= 62514]
2020-02-27 14:48:26.139 INFO 22132 --- [-1.customer-1-1] o.s.i.h.s.MessagingMethodInvokerHelper : Overriding default instance of MessageHandlerMethodFactory with provided one.
2020-02-27 14:48:26.140 INFO 22132 --- [-1.customer-1-1] com.example.demo.TestSink : Data received customer-1...body
2020-02-27 14:49:14.185 INFO 22132 --- [ consumer.app-1] o.s.i.h.s.MessagingMethodInvokerHelper : Overriding default instance of MessageHandlerMethodFactory with provided one.
2020-02-27 14:49:14.194 INFO 22132 --- [ consumer.app-1] com.example.demo.TestConsumer : ==============>consume2 messge [[payload=ok, headers={amqp_receivedDeliveryMode=NON_PERSISTENT, amqp_receivedRoutingKey=#, amqp_receivedExchange=consumer, amqp_deliveryTag=1, deliveryAttempt=1, amqp_consumerQueue=consumer.app, amqp_redelivered=false, id=33581edb-2832-1c92-b765-a05794512b34, spring.cloud.function.definition=consume1, amqp_consumerTag=amq.ctag-RIp2nZdcG2a0hNQeImwtBw, sourceData=(Body:'[B#8159793(byte[2])' MessageProperties [headers={spring.cloud.function.definition=consume1}, contentLength=0, receivedDeliveryMode=NON_PERSISTENT, redelivered=false, receivedExchange=consumer, receivedRoutingKey=#, deliveryTag=1, consumerTag=amq.ctag-RIp2nZdcG2a0hNQeImwtBw, consumerQueue=consumer.app]), timestamp=1582811354186}]]
2020-02-27 14:49:14.203 INFO 22132 --- [oundedElastic-1] o.s.i.monitor.IntegrationMBeanExporter : Registering MessageChannel customer-2
2020-02-27 14:49:14.213 INFO 22132 --- [oundedElastic-1] o.s.c.s.m.DirectWithAttributesChannel : Channel 'application.customer-2' has 1 subscriber(s).
2020-02-27 14:49:14.216 INFO 22132 --- [-2.customer-2-1] o.s.i.h.s.MessagingMethodInvokerHelper : Overriding default instance of MessageHandlerMethodFactory with provided one.
2020-02-27 14:49:14.216 INFO 22132 --- [-2.customer-2-1] com.example.demo.TestSink : Data received customer-2...body
application.yml
spring:
main:
allow-bean-definition-overriding: true
spring.cloud.stream:
function.definition: supplier;receive1;receive2;consume1;consume2
function.routing:
enabled: true
bindings:
consume1-in-0.destination: consumer
consume1-in-0.group: app
consume2-in-0.destination: consumer
consume2-in-0.group: app
receive1-in-0.destination: customer-1
receive1-in-0.group: customer-1
receive2-in-0.destination: customer-2
receive2-in-0.group: customer-2
DemoApplication.java
import com.fasterxml.jackson.databind.ObjectMapper
import org.apache.commons.logging.Log
import org.apache.commons.logging.LogFactory
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.context.annotation.Bean
import org.springframework.http.HttpStatus
import org.springframework.messaging.Message
import org.springframework.messaging.support.MessageBuilder
import org.springframework.stereotype.Component
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestMethod.GET
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.client.RestTemplate
import reactor.core.publisher.EmitterProcessor
import reactor.core.publisher.Flux
import java.util.function.Consumer
import java.util.function.Supplier
#SpringBootApplication
class DemoApplication
fun main(args: Array<String>) {
runApplication<DemoApplication>(*args)
}
#RestController
class DynamicDestinationController(private val jsonMapper: ObjectMapper) {
private val processor: EmitterProcessor<Message<String>> = EmitterProcessor.create<Message<String>>()
#RequestMapping(path = ["/api/dest/{destName}"], method = [GET], consumes = ["*/*"])
#ResponseStatus(HttpStatus.ACCEPTED)
fun handleRequest(#PathVariable destName:String) {
val message: Message<String> = MessageBuilder.withPayload("body")
.setHeader("spring.cloud.stream.sendto.destination", destName).build()
processor.onNext(message)
}
#Bean
fun supplier(): Supplier<Flux<Message<String>>> {
return Supplier { processor }
}
}
const val destResourceUrl = "http://localhost:8080/api/dest"
#Component
class TestConsumer() {
private val restTemplate: RestTemplate = RestTemplate()
private val logger: Log = LogFactory.getLog(javaClass)
#Bean
fun consume1(): Consumer<Message<String>> = Consumer {
logger.info("==============>consume1 messge [[payload=${it.payload}, headers=${it.headers}]]")
restTemplate.getForEntity("$destResourceUrl/customer-1", String::class.java)
}
#Bean
fun consume2(): Consumer<Message<String>> = Consumer {
logger.info("==============>consume2 messge [[payload=${it.payload}, headers=${it.headers}]]")
restTemplate.getForEntity("$destResourceUrl/customer-2", String::class.java)
}
}
#Component
class TestSink {
private val logger: Log = LogFactory.getLog(javaClass)
#Bean
fun receive1(): Consumer<String> = Consumer {
logger.info("Data received customer-1..." + it);
}
#Bean
fun receive2(): Consumer<String> = Consumer {
logger.info("Data received customer-2..." + it);
}
}
Any idea how to fix the route to consumer?
thanks in advance.
demo-repo
Actually I am a bit confused, so let's do one step at the time. Here is the functioning (modelled after yours) app which uses sendto feature allowing you to send messages to the specific (existing and/or dynamically resolved) destinations.
(in java but you can rework it to Kotlin)
#Controller
public class WebSourceApplication {
public static void main(String[] args) {
SpringApplication.run(WebSourceApplication.class,
"--spring.cloud.function.definition=supplier;consA;consB",
"--spring.cloud.stream.bindings.consA-in-0.destination=consumerA",
"--spring.cloud.stream.bindings.consA-in-0.group=consumerA-grp",
"--spring.cloud.stream.bindings.consB-in-0.destination=consumerB",
"--spring.cloud.stream.bindings.consB-in-0.group=consumerB-grp"
);
}
EmitterProcessor<Message<String>> processor = EmitterProcessor.create();
#RequestMapping(path = "/api/dest/{destName}", consumes = "*/*")
#ResponseStatus(HttpStatus.ACCEPTED)
public void delegateToSupplier(#RequestBody String body, #PathVariable String destName) {
Message<String> message = MessageBuilder.withPayload(body)
.setHeader("spring.cloud.stream.sendto.destination", destName)
.build();
processor.onNext(message);
}
#Bean
public Supplier<Flux<Message<String>>> supplier() {
return () -> processor;
}
#Bean
public Consumer<String> consA() {
return v -> {
System.out.println("Consuming from consA: " + v);
};
}
#Bean
public Consumer<String> consB() {
return v -> {
System.out.println("Consuming from consB: " + v);
};
}
}
And when i curl it i get consistent invocation pr the appropriate consumer:
curl -H "Content-Type: application/json" -X POST -d "Hello Spring Cloud Stream" http://localhost:8080/api/dest/consumerA
log: Consuming from consA: Hello Spring Cloud Stream
. . .
curl -H "Content-Type: application/json" -X POST -d "Hello Spring Cloud Stream" http://localhost:8080/api/dest/consumerB
log: Consuming from consB: Hello Spring Cloud Stream
Notice: There is no enable routing property. That feature is mainly aimed to always call one function functionRouter and have it call other functions on your behalf. It is a feature of spring-cloud-function which means it works outside of spring-cloud-srteam and channels/destinations etc.
Isn't that what you are trying to accomplish? Send message to a different destination based on some oath variable in your HTTP request?
Here is an example of a different microservice which receives on routing function which hen routes to different functions
public class FunctionRoutingApplication {
public static void main(String[] args) {
SpringApplication.run(FunctionRoutingApplication.class,
"--spring.cloud.stream.function.routing.enabled=true"
);
}
#Bean
public Consumer<String> consA() {
return v -> {
System.out.println("Consuming from consA: " + v);
};
}
#Bean
public Consumer<String> consB() {
return v -> {
System.out.println("Consuming from consB: " + v);
};
}
}
And that's pretty much it. Go to your broker and send data to functionRouter-in-0 exchange while providing spring.cloud.function.definition=consA/consB headers and you will see consistent invocations.
Am I still missing something?

My tcp client using spring integration not able to get response

I have created tcp client using spring integration I am able to receive response for my send message . But when I uses localDateTime.now() to log time I am not able to receive the response of send message . I know this can be solved using time setting to make thread wait. As I am new to spring integration So kindly help me how to do it.
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Test
{
protected final Log logger = LogFactory.getLog(this.getClass());
// **************** Client **********************************************
#Bean
public MessageChannel replyChannel()
{
return new DirectChannel();
}
#Bean
public MessageChannel sendChannel()
{
MessageChannel directChannel = new DirectChannel();
return directChannel;
}
#EnableIntegration
#IntegrationComponentScan
#Configuration
public static class config
{
#MessagingGateway(defaultRequestChannel = "sendChannel", defaultReplyChannel = "replyChannel")
public interface Gateway
{
String Send(String in);
}
}
#Bean
AbstractClientConnectionFactory tcpNetClientConnectionFactory()
{
AbstractClientConnectionFactory tcpNetClientConnectionFactory = new TcpNetClientConnectionFactory("localhost",
9999);
tcpNetClientConnectionFactory.setSerializer(new UCCXImprovedSerializer());
tcpNetClientConnectionFactory.setDeserializer(new UCCXImprovedSerializer());
tcpNetClientConnectionFactory.setSingleUse(true);
tcpNetClientConnectionFactory.setMapper(new TcpMessageMapper());
return tcpNetClientConnectionFactory;
}
#Bean
#ServiceActivator(inputChannel = "sendChannel")
TcpOutboundGateway tcpOutboundGateway()
{
TcpOutboundGateway tcpOutboundGateway = new TcpOutboundGateway();
tcpOutboundGateway.setConnectionFactory(tcpNetClientConnectionFactory());
tcpOutboundGateway.setReplyChannel(replyChannel());
return tcpOutboundGateway;
}
public static void main(String args[])
{
// new LegaServer();
ConfigurableApplicationContext applicationContext = SpringApplication.run(Test.class, args);
String temp = applicationContext.getBean(Gateway.class).Send("kksingh");
System.out.println(LocalDateTime.now()+"output" + temp);
applicationContext.stop();
}
}
My custom serialzer and deserialser UCCXImprovedSerializerclass
after updating as per #Garry
public class UCCXImprovedSerializer implements Serializer<String>, Deserializer<String>
{
#Override
public String deserialize(InputStream initialStream) throws IOException
{
System.out.println("deserialzier called");
StringBuilder sb = new StringBuilder();
try (BufferedReader rdr = new BufferedReader(new InputStreamReader(initialStream)))
{
for (int c; (c = rdr.read()) != -1;)
{
sb.append((char) c);
}
}
return sb.toString();
}
#Override
public void serialize(String msg, OutputStream os) throws IOException
{
System.out.println(msg + "---serialize---" + Thread.currentThread().getName() + "");
os.write(msg.getBytes());
}
}
My server at port 9999 code
try
{
clientSocket = echoServer.accept();
System.out.println("client connection established..");
is = new DataInputStream(clientSocket.getInputStream());
os = new PrintStream(clientSocket.getOutputStream());
String tempString = "kksingh";
byte[] tempStringByte = tempString.getBytes();
byte[] temp = new byte[tempString.getBytes().length];
while (true)
{
is.read(temp);
System.out.println(new String(temp) + "--received msg is--- " + LocalDateTime.now());
System.out.println(LocalDateTime.now() + "sending value");
os.write(tempStringByte);
break;
}
} catch (IOException e)
{
System.out.println(e);
}
}
My log file for tcp client
2017-06-04 23:10:14.771 INFO 15568 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started org.springframework.integration.endpoint.EventDrivenConsumer#1f12e153
kksingh---serialize---main
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
2017-06-04 23:10:14.812 ERROR 15568 --- [pool-1-thread-1] o.s.i.ip.tcp.TcpOutboundGateway : Cannot correlate response - no pending reply for localhost:9999:57622:bc98ee29-8957-47bd-bd8a-f734c3ec3f9d
2017-06-04T23:10:14.809output
2017-06-04 23:10:14.821 INFO 15568 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 0
My log file for server side
client connection established..
kksingh--received msg is--- 2017-06-04T23:10:14.899
2017-06-04T23:10:14.899sending value
when I removed the localdatetime.now() from server and tcpclient I am able to get response as outputkksingh
o.s.i.endpoint.EventDrivenConsumer : Adding {logging-channel-adapter:_org.springframework.integration.errorLogger} as a subscriber to the 'errorChannel' channel
2017-06-05 12:46:32.494 INFO 29076 --- [ main] o.s.i.channel.PublishSubscribeChannel : Channel 'application.errorChannel' has 1 subscriber(s).
2017-06-05 12:46:32.495 INFO 29076 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started _org.springframework.integration.errorLogger
2017-06-05 12:46:32.746 INFO 29076 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-06-05 12:46:32.753 INFO 29076 --- [ main] o.s.i.samples.tcpclientserver.Test : Started Test in 2.422 seconds (JVM running for 2.716)
2017-06-05 12:46:32.761 INFO 29076 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Adding {bridge:null} as a subscriber to the 'replyChannel' channel
2017-06-05 12:46:32.762 INFO 29076 --- [ main] o.s.integration.channel.DirectChannel : Channel 'application.replyChannel' has 1 subscriber(s).
2017-06-05 12:46:32.763 INFO 29076 --- [ main] o.s.i.endpoint.EventDrivenConsumer : started org.springframework.integration.endpoint.EventDrivenConsumer#1f12e153
kksingh---serialize---main
pool-1-thread-1---deserialize----kksingh
outputkksingh
2017-06-05 12:46:32.837 INFO 29076 --- [ main] o.s.c.support.DefaultLifecycleProcessor : Stopping beans in phase 0
2017-06-05 12:46:32.839 INFO 29076 --- [ main] o.s.i.endpoint.EventDrivenConsumer : Removing {bridge:null} as a subscriber to the 'replyChannel' channel
2017-06-05 12:46:32.839 INFO 29076 --- [
Your deserializer is deserializing multiple packets...
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
pool-1-thread-1---deserialize----
Which produces 4 reply messsages; the gateway can only handle one reply which is why you see that ERROR message.
You deserializer needs to be smarter than just capturing "available" bytes. You need something in the message to indicate the end of the data (or close the socket to indicate the end).

Spring Cloud: Eureka Client registration/deregistration cycle

To familiarize myself with Spring Cloud's Eureka client/server mechanism, I try to connect a client to the Eureka server and toggle the connection on/off every 5 minutes to see how the Eureka server handles this.
I have two Eureka clients.
The first on is giving me information about the registered applications with this code:
#Autowired
private DiscoveryClient discoveryClient;
#RequestMapping(value = "/services", produces = MediaType.APPLICATION_JSON)
public ResponseEntity<ResourceSupport> applications() {
ResourceSupport resource = new ResourceSupport();
Set<String> regions = discoveryClient.getAllKnownRegions();
for (String region : regions) {
Applications allApps = discoveryClient.getApplicationsForARegion(region);
List<Application> registeredApps = allApps.getRegisteredApplications();
Iterator<Application> it = registeredApps.iterator();
while (it.hasNext()) {
Application app = it.next();
List<InstanceInfo> instancesInfos = app.getInstances();
if (instancesInfos != null && !instancesInfos.isEmpty()) {
//only show one of the instances
InstanceInfo info = instancesInfos.get(0);
resource.add(new Link(info.getHomePageUrl(), "urls"));
}
}
}
return new ResponseEntity<ResourceSupport>(resource, HttpStatus.OK);
}
The second Eureka Client register/deregister itself every 5 minutes:
private static final long EUREKA_INTERVAL = 5 * 60000;
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(MyServiceApplication.class);
long currentTime = System.currentTimeMillis();
long lastToggleTime = System.currentTimeMillis();
boolean connected = true;
while (true) {
if (currentTime - lastToggleTime > EUREKA_INTERVAL) {
if (connected) {
System.err.println("disconnect");
DiscoveryManager.getInstance().shutdownComponent();
connected = false;
lastToggleTime = System.currentTimeMillis();
}
else {
System.err.println("connect");
DiscoveryManager.getInstance().initComponent(
DiscoveryManager.getInstance().getEurekaInstanceConfig(),
DiscoveryManager.getInstance().getEurekaClientConfig());
connected = true;
lastToggleTime = System.currentTimeMillis();
}
}
currentTime = System.currentTimeMillis();
}
}
The log output of the second Eureka client looks as follows:
disconnect
2015-03-26 13:59:23.713 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : DiscoveryClient_MYAPPNAME/MYMEGAHOSTNAME - deregister status: 200
connect
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Disable delta property : false
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Single vip registry refresh property : null
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Force full registry fetch : false
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Application is null : false
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Registered Applications size is zero : true
2015-03-26 14:04:23.870 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Application version is -1: true
2015-03-26 14:04:23.889 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Getting all instance registry info from the eureka server
2015-03-26 14:04:23.892 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : The response status is 200
2015-03-26 14:04:23.894 INFO 3452 --- [ main] com.netflix.discovery.DiscoveryClient : Starting heartbeat executor: renew interval is: 30
2015-03-26 14:04:53.916 INFO 3452 --- [ool-11-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_MYAPPNAME/MYMEGAHOSTNAME - Re-registering apps/MYAPPNAME
2015-03-26 14:04:53.916 INFO 3452 --- [ool-11-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_MYAPPNAME/MYMEGAHOSTNAME: registering service...
2015-03-26 14:04:53.946 INFO 3452 --- [ool-11-thread-1] com.netflix.discovery.DiscoveryClient : DiscoveryClient_MYAPPNAME/MYMEGAHOSTNAME - registration status: 204
When starting both Eureka clients for the first time, this works well. The second client is shown by the first client AND the second client is visible in die Eureka server console.
When the second client disconnects itself from the Eureka server, it is no longer listed there and the first client is also not showing it anymore.
Unfortunately, when the second client reconnects to the Eureka server, the Eureka server console is just showing a big red highlighted "DOWN (1)" and the first client is not showing the second client anymore. What am I missing here?
Solution:
Based on Dave Sayer's answer my solution was to add a custom #Configuration that has the EurekaDiscoveryClientConfiguration autowired and starts a thread for toggling the registration. Note that this is for test purposes only, so it may be a quite ugly solution ;-)
#Configuration
static public class MyDiscoveryClientConfigServiceAutoConfiguration {
#Autowired
private EurekaDiscoveryClientConfiguration lifecycle;
#PostConstruct
public void init() {
new Thread(new Runnable() {
#Override
public void run() {
long currentTime = System.currentTimeMillis();
long lastToggleTime = System.currentTimeMillis();
boolean connected = true;
while (true) {
if (currentTime - lastToggleTime > EUREKA_INTERVAL) {
if (connected) {
System.err.println("disconnect");
lifecycle.stop();
DiscoveryManager.getInstance().getDiscoveryClient().shutdown();
connected = false;
lastToggleTime = System.currentTimeMillis();
}
else {
System.err.println("connect");
DiscoveryManager.getInstance().initComponent(
DiscoveryManager.getInstance().getEurekaInstanceConfig(),
DiscoveryManager.getInstance().getEurekaClientConfig());
lifecycle.start();
connected = true;
lastToggleTime = System.currentTimeMillis();
}
}
currentTime = System.currentTimeMillis();
}
}
}).start();
}
}
Your call to DiscoveryManager.getInstance().initComponent() does not set the status (and the default is DOWN). In Spring Cloud we handle it in a special EurekaDiscoveryClientConfiguration.start() lifecycle. You could inject that and re-use it like this:
#Autowired
private EurekaDiscoveryClientConfiguration lifecycle;
#PostConstruct
public void init() {
this.lifecycle.stop();
if (DiscoveryManager.getInstance().getDiscoveryClient() != null) {
DiscoveryManager.getInstance().getDiscoveryClient().shutdown();
}
ApplicationInfoManager.getInstance().initComponent(this.instanceConfig);
DiscoveryManager.getInstance().initComponent(this.instanceConfig,
this.clientConfig);
this.lifecycle.start();
}
(which is code taken from here: https://github.com/spring-cloud/spring-cloud-netflix/blob/master/spring-cloud-netflix-core/src/main/java/org/springframework/cloud/netflix/config/DiscoveryClientConfigServiceAutoConfiguration.java#L58).

Resources