@MetaData Annotations on Custom Gateway Interfaces

I would like to provide commands with system-relevant metadata like user and correlation ids in order to improve traceability of command authorization and flow so that I can better manage these cross-cutting concerns.

I am trying to setup a custom gateway interface, but seem to be encountering issues around event metadata immutability. I am trying an approach outlined in the reference guide at 3.1.2 involving a custom interface as follows:

public interface AuthorizedCommandGateway extends CommandGateway {
  public void sendAuthorized(Object command, @MetaData("userId") String userId);
}

But when I attempt to dispatch a command with metadata through this interface, I encounter an "Event meta-data is immutable" exception.

Since this didn't seem to be working, I tried wrapping the commands in an appropriately-parameterized CommandMessage and applying the desired metadata properties to the CommandMessage before sending it through a standard CommandGateway. However I encounter the same exception in this case as well.

*How is it possible to effectively incorporate metadata onto commands before they are sent?* 

At this point I am now considering investigating using a custom UnitOfWorkListener which might be able to intercept a command's metadata and cast to a mutable metadata instance, providing the necessary values from ThreadLocal. But it seems like this may also not be possible given the concurrency model, and to include it on every command it seems we would also have to implement a custom command bus -- so this approach is starting to seem somewhat involved. 

I would definitely appreciate any guidance that you may be able to provide here in terms of techniques to provide commands with metadata. Thank you!

Hi Joseph,

the exception doesn’t seem to make sense in this case. Could you provide a stack trace, so that I can investigate what’s going wrong?

The Gateway interface you created looks fine. I am doing the same (also for user ID) in my own projects.

Please also specify the version of Axon you’re using.

Cheers,

Allard

We are using Axon 2.4 – please find below the stack trace (very slightly anonymized.)

{
“timestamp” : “2015-05-07T13:42:54.095”,
“message” : “Exception in command execution”,
“throwable” : {
“message” : “Command execution resulted in a checked exception that was not declared on the gateway”,
“cause” : {
“message” : “Error while deserializing object”,
“cause” : {
“message” : “Event meta-data is immutable.”,
“cause” : {
“message” : “Event meta-data is immutable.”,
“class” : “java.lang.Throwable”,
“stackTrace” : [
“org.axonframework.domain.MetaData.put(MetaData.java:94)”,
“org.axonframework.domain.MetaData.put(MetaData.java:33)”,
“com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:467)”
]
},
“class” : “java.lang.Throwable”,
“stackTrace” : [
“com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:210)”,
“com.fasterxml.jackson.databind.JsonMappingException.wrapWithPath(JsonMappingException.java:177)”,
“com.fasterxml.jackson.databind.deser.std.MapDeserializer.wrapAndThrow(MapDeserializer.java:566)”,
“com.fasterxml.jackson.databind.deser.std.MapDeserializer._readAndBindStringMap(MapDeserializer.java:472)”,
“com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:320)”,
“com.fasterxml.jackson.databind.deser.std.MapDeserializer.deserialize(MapDeserializer.java:26)”,
“com.fasterxml.jackson.databind.ObjectReader._bindAndClose(ObjectReader.java:1269)”,
“com.fasterxml.jackson.databind.ObjectReader.readValue(ObjectReader.java:912)”,
“org.axonframework.serializer.json.JacksonSerializer.deserialize(JacksonSerializer.java:234)”
]
},
“class” : “org.axonframework.serializer.SerializationException”,
“stackTrace” : [
“org.axonframework.serializer.json.JacksonSerializer.deserialize(JacksonSerializer.java:236)”,
“org.axonframework.serializer.MessageSerializer.deserialize(MessageSerializer.java:124)”,
“org.axonframework.commandhandling.distributed.jgroups.DispatchMessage.getCommandMessage(DispatchMessage.java:106)”,
“org.axonframework.commandhandling.distributed.jgroups.JGroupsConnector$MessageReceiver.processDispatchMessage(JGroupsConnector.java:344)”,
“org.axonframework.commandhandling.distributed.jgroups.JGroupsConnector$MessageReceiver.receive(JGroupsConnector.java:336)”,
“org.jgroups.JChannel.invokeCallback(JChannel.java:787)”,
“org.jgroups.JChannel.up(JChannel.java:711)”,
“org.jgroups.stack.ProtocolStack.up(ProtocolStack.java:1015)”,
“org.jgroups.protocols.pbcast.STATE_TRANSFER.up(STATE_TRANSFER.java:178)”,
“org.jgroups.protocols.FRAG2.up(FRAG2.java:165)”,
“org.jgroups.protocols.FlowControl.up(FlowControl.java:381)”,
“org.jgroups.protocols.pbcast.GMS.up(GMS.java:1010)”,
“org.jgroups.protocols.pbcast.STABLE.up(STABLE.java:234)”,
“org.jgroups.protocols.pbcast.NAKACK.up(NAKACK.java:636)”,
“org.jgroups.protocols.BARRIER.up(BARRIER.java:103)”,
“org.jgroups.protocols.VERIFY_SUSPECT.up(VERIFY_SUSPECT.java:147)”,
“org.jgroups.protocols.FD.up(FD.java:255)”,
“org.jgroups.protocols.FD_SOCK.up(FD_SOCK.java:301)”,
“org.jgroups.protocols.MERGE2.up(MERGE2.java:209)”,
“org.jgroups.protocols.Discovery.up(Discovery.java:379)”,
“org.jgroups.protocols.MPING.up(MPING.java:181)”,
“org.jgroups.protocols.TP.passMessageUp(TP.java:1404)”,
“org.jgroups.protocols.TP$4.run(TP.java:1332)”,
“java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)”,
“java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)”
]
},
“class” : “org.axonframework.commandhandling.CommandExecutionException”,
“stackTrace” : [
“org.axonframework.commandhandling.gateway.GatewayProxyFactory$WrapNonDeclaredCheckedExceptions.invoke(GatewayProxyFactory.java:524)”,
“org.axonframework.commandhandling.gateway.GatewayProxyFactory$GatewayInvocationHandler.invoke(GatewayProxyFactory.java:423)”,
“com.sun.proxy.$Proxy77.sendAndWait(Unknown Source)”,
“[com.xyz.MyAggregate]Controller.createMyAggregate”,
“[com.xyz.MyAggregate]Controller.createMyAggregate”,
“sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)”,
“sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)”,
“sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)”,
“java.lang.reflect.Method.invoke(Method.java:606)”,
“org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1270)”,
“org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:221)”,
“org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137)”,
“org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:110)”,
“org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:777)”,
“org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:706)”,
“org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:85)”,
“org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:943)”,
“org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:877)”,
“org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:966)”,
“org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:868)”,
“javax.servlet.http.HttpServlet.service(HttpServlet.java:644)”,
“org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:842)”,
“javax.servlet.http.HttpServlet.service(HttpServlet.java:725)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration$ApplicationContextHeaderFilter.doFilterInternal(EndpointWebMvcAutoConfiguration.java:291)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)”,
“org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118)”,
“org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.authentication.www.BasicAuthenticationFilter.doFilter(BasicAuthenticationFilter.java:201)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)”,
“org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)”,
“org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“[com.xyz].web.configuration.SimpleCORSFilter.doFilter(SimpleCORSFilter.java:28)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.springframework.boot.actuate.trace.WebRequestTraceFilter.doFilterInternal(WebRequestTraceFilter.java:100)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.springframework.boot.actuate.autoconfigure.MetricFilterAutoConfiguration$MetricsFilter.doFilterInternal(MetricFilterAutoConfiguration.java:90)”,
“org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)”,
“org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239)”,
“org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)”,
“org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:219)”,
“org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)”,
“org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)”,
“org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:142)”,
“org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)”,
“org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)”,
“org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:537)”,
“org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1085)”,
“org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:658)”,
“org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)”,
“org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1556)”,
“org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1513)”,
“java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)”,
“java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)”,
“org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)”,
“java.lang.Thread.run(Thread.java:745)”
]
},
“level” : “ERROR”,
“thread” : “http-nio-8084-exec-1”,
“logger” : “[com.xyz.MyAggregate]Controller”
}

Hi Joseph,

looking at the stacktrace, it seems that the issue is unrelated to the CommandGateway. It looks like jackson has difficulty deserializing the MetaData, as it tries to treat it as a regular map.

Unfortunately, it seems that this flaw is located in Axon. The good news is that the solution is simple:

public class MetaDataDeserializer extends JsonDeserializer {

@SuppressWarnings(“unchecked”)
@Override
public MetaData deserialize(JsonParser jp, DeserializationContext ctxt)
throws IOException {
JsonDeserializer deserializer = ctxt.findRootValueDeserializer(
ctxt.getTypeFactory().constructMapType(HashMap.class, String.class, String.class));

return MetaData.from((Map) deserializer.deserialize(jp, ctxt));
}
}

You can register this deserializer with the JacksonSerializer as follows:

serializer.getObjectMapper().registerModule(
new SimpleModule(“Some-module name”)
.addDeserializer(MetaData.class, new MetaDataDeserializer()));

I’ll add a fix for this in Axon 2.4.2
Cheers,

Allard